diff --git a/.solhint.json b/.solhint.json index 84661698..73e3a169 100644 --- a/.solhint.json +++ b/.solhint.json @@ -5,6 +5,8 @@ "avoid-suicide": "error", "avoid-sha3": "warn", "compiler-version": ["warn", "^0.8.6"], - "func-visibility": ["warn", { "ignoreConstructors": true }] + "func-visibility": ["warn", { "ignoreConstructors": true }], + "not-rely-on-time": "off", + "no-empty-blocks": "off" } } diff --git a/contracts/governance/staking/GamifiedManager.sol b/contracts/governance/staking/GamifiedManager.sol new file mode 100644 index 00000000..cf0985df --- /dev/null +++ b/contracts/governance/staking/GamifiedManager.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity 0.8.6; + +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "./GamifiedTokenStructs.sol"; + +/** + * @title GamifiedManager + * @author mStable + * @notice library to reduce the size of the GamifiedToken contract. + * @dev VERSION: 1.0 + * DATE: 2021-08-11 + */ +library GamifiedManager { + event QuestAdded( + address questMaster, + uint256 id, + QuestType model, + uint16 multiplier, + QuestStatus status, + uint32 expiry + ); + event QuestComplete(address indexed user, uint256 indexed id); + event QuestExpired(uint16 indexed id); + event QuestSeasonEnded(); + + /*************************************** + QUESTS + ****************************************/ + + /** + * @dev Called by questMasters to add a new quest to the system with default 'ACTIVE' status + * @param _model Type of quest rewards multiplier (does it last forever or just for the season). + * @param _multiplier Multiplier, from 1 == 1.01x to 100 == 2.00x + * @param _expiry Timestamp at which quest expires. Note that permanent quests should still be given a timestamp. + */ + function addQuest( + Quest[] storage _quests, + QuestType _model, + uint16 _multiplier, + uint32 _expiry + ) external { + require(_expiry > block.timestamp + 1 days, "Quest window too small"); + require(_multiplier > 0 && _multiplier <= 50, "Quest multiplier too large > 1.5x"); + + _quests.push( + Quest({ + model: _model, + multiplier: _multiplier, + status: QuestStatus.ACTIVE, + expiry: _expiry + }) + ); + + emit QuestAdded( + msg.sender, + _quests.length - 1, + _model, + _multiplier, + QuestStatus.ACTIVE, + _expiry + ); + } + + /** + * @dev Called by questMasters to expire a quest, setting it's status as EXPIRED. After which it can + * no longer be completed. + * @param _id Quest ID (its position in the array) + */ + function expireQuest(Quest[] storage _quests, uint16 _id) external { + require(_id < _quests.length, "Quest does not exist"); + require(_quests[_id].status == QuestStatus.ACTIVE, "Quest already expired"); + + _quests[_id].status = QuestStatus.EXPIRED; + if (block.timestamp < _quests[_id].expiry) { + _quests[_id].expiry = SafeCast.toUint32(block.timestamp); + } + + emit QuestExpired(_id); + } + + /** + * @dev Called by questMasters to start a new quest season. After this, all current + * seasonMultipliers will be reduced at the next user action (or triggered manually). + * In order to reduce cost for any keepers, it is suggested to add quests at the start + * of a new season to incentivise user actions. + * A new season can only begin after 9 months has passed. + */ + function startNewQuestSeason(uint32 seasonEpoch, Quest[] storage _quests) external { + require(block.timestamp > (seasonEpoch + 39 weeks), "Season has not elapsed"); + + uint256 len = _quests.length; + for (uint256 i = 0; i < len; i++) { + Quest memory quest = _quests[i]; + if (quest.model == QuestType.SEASONAL) { + require( + quest.status == QuestStatus.EXPIRED || block.timestamp > quest.expiry, + "All seasonal quests must have expired" + ); + } + } + + emit QuestSeasonEnded(); + } +} diff --git a/contracts/governance/staking/GamifiedToken.sol b/contracts/governance/staking/GamifiedToken.sol index 9330ba37..e4f304cc 100644 --- a/contracts/governance/staking/GamifiedToken.sol +++ b/contracts/governance/staking/GamifiedToken.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity 0.8.6; -import { ILockedERC20 } from "./interfaces/ILockedERC20.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import { HeadlessStakingRewards } from "../../rewards/staking/HeadlessStakingRewards.sol"; import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; -import { SignatureVerifier } from "./deps/SignatureVerifier.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { ILockedERC20 } from "./interfaces/ILockedERC20.sol"; +import { SignatureVerifier } from "./deps/SignatureVerifier.sol"; +import { HeadlessStakingRewards } from "../../rewards/staking/HeadlessStakingRewards.sol"; +import { GamifiedManager } from "./GamifiedManager.sol"; import "./GamifiedTokenStructs.sol"; /** @@ -27,9 +28,10 @@ abstract contract GamifiedToken is ILockedERC20, Initializable, ContextUpgradeable, - SignatureVerifier, HeadlessStakingRewards { + /// @notice address that signs user quests have been completed + address public immutable _signer; /// @notice name of this token (ERC20) string public override name; /// @notice symbol of this token (ERC20) @@ -66,15 +68,17 @@ abstract contract GamifiedToken is ****************************************/ /** - * @param _signer Signer address is used to verify completion of quests off chain + * @param _signerArg Signer address is used to verify completion of quests off chain * @param _nexus System nexus * @param _rewardsToken Token that is being distributed as a reward. eg MTA */ constructor( - address _signer, + address _signerArg, address _nexus, address _rewardsToken - ) SignatureVerifier(_signer) HeadlessStakingRewards(_nexus, _rewardsToken) {} + ) HeadlessStakingRewards(_nexus, _rewardsToken) { + _signer = _signerArg; + } /** * @param _nameArg Token name @@ -97,10 +101,14 @@ abstract contract GamifiedToken is * @dev Checks that _msgSender is either governor or the quest master */ modifier questMasterOrGovernor() { - require(_msgSender() == _questMaster || _msgSender() == _governor(), "Not verified"); + _questMasterOrGovernor(); _; } + function _questMasterOrGovernor() internal view { + require(_msgSender() == _questMaster || _msgSender() == _governor(), "Not verified"); + } + /*************************************** VIEWS ****************************************/ @@ -157,26 +165,19 @@ abstract contract GamifiedToken is } /** - * @dev Raw balance data + * @notice Raw staked balance without any multipliers */ function balanceData(address _account) external view returns (Balance memory) { return _balances[_account]; } /** - * @dev Gets raw quest data + * @notice Gets raw quest data */ function getQuest(uint256 _id) external view returns (Quest memory) { return _quests[_id]; } - /** - * @dev Gets a users quest completion status - */ - function getQuestCompletion(address _account, uint256 _id) external view returns (bool) { - return _questCompletion[_account][_id]; - } - /*************************************** QUESTS ****************************************/ @@ -192,26 +193,7 @@ abstract contract GamifiedToken is uint16 _multiplier, uint32 _expiry ) external questMasterOrGovernor { - require(_expiry > block.timestamp + 1 days, "Quest window too small"); - require(_multiplier > 0 && _multiplier <= 50, "Quest multiplier too large > 1.5x"); - - _quests.push( - Quest({ - model: _model, - multiplier: _multiplier, - status: QuestStatus.ACTIVE, - expiry: _expiry - }) - ); - - emit QuestAdded( - _msgSender(), - _quests.length - 1, - _model, - _multiplier, - QuestStatus.ACTIVE, - _expiry - ); + GamifiedManager.addQuest(_quests, _model, _multiplier, _expiry); } /** @@ -220,15 +202,7 @@ abstract contract GamifiedToken is * @param _id Quest ID (its position in the array) */ function expireQuest(uint16 _id) external questMasterOrGovernor { - require(_quests.length >= _id, "Quest does not exist"); - require(_quests[_id].status == QuestStatus.ACTIVE, "Quest already expired"); - - _quests[_id].status = QuestStatus.EXPIRED; - if (block.timestamp < _quests[_id].expiry) { - _quests[_id].expiry = SafeCast.toUint32(block.timestamp); - } - - emit QuestExpired(_id); + GamifiedManager.expireQuest(_quests, _id); } /** @@ -239,22 +213,10 @@ abstract contract GamifiedToken is * A new season can only begin after 9 months has passed. */ function startNewQuestSeason() external questMasterOrGovernor { - require(block.timestamp > (seasonEpoch + 39 weeks), "Season has not elapsed"); - - uint256 len = _quests.length; - for (uint256 i = 0; i < len; i++) { - Quest memory quest = _quests[i]; - if (quest.model == QuestType.SEASONAL) { - require( - quest.status == QuestStatus.EXPIRED || block.timestamp > quest.expiry, - "All seasonal quests must have expired" - ); - } - } + GamifiedManager.startNewQuestSeason(seasonEpoch, _quests); + // Have to set storage variable here as it can't be done in the library function seasonEpoch = SafeCast.toUint32(block.timestamp); - - emit QuestSeasonEnded(); } /** @@ -276,7 +238,10 @@ abstract contract GamifiedToken is for (uint256 i = 0; i < len; i++) { require(_validQuest(_ids[i]), "Err: Invalid Quest"); require(!hasCompleted(_account, _ids[i]), "Err: Already Completed"); - require(verify(_account, _ids[i], _signatures[i]), "Err: Invalid Signature"); + require( + SignatureVerifier.verify(_signer, _account, _ids[i], _signatures[i]), + "Err: Invalid Signature" + ); // store user quest has completed _questCompletion[_account][_ids[i]] = true; @@ -304,7 +269,7 @@ abstract contract GamifiedToken is */ function _validQuest(uint256 _id) internal view returns (bool) { return - _quests.length >= _id && + _id < _quests.length && _quests[_id].status == QuestStatus.ACTIVE && block.timestamp < _quests[_id].expiry; } @@ -454,12 +419,12 @@ abstract contract GamifiedToken is * Importantly, when a user stakes more, their weightedTimestamp is reduced proportionate to their stake. * @param _account Address of user to credit * @param _rawAmount Raw amount of tokens staked - * @param _exitCooldown Reset the users cooldown slash + * @param _newPercentage Set the users cooldown slash */ function _mintRaw( address _account, uint256 _rawAmount, - bool _exitCooldown + uint256 _newPercentage ) internal virtual updateReward(_account) { require(_account != address(0), "ERC20: mint to the zero address"); @@ -468,7 +433,7 @@ abstract contract GamifiedToken is _balances[_account].raw = oldBalance.raw + SafeCast.toUint128(_rawAmount); // 2. Exit cooldown if necessary - if (_exitCooldown) _balances[_account].cooldownMultiplier = 0; + _balances[_account].cooldownMultiplier = SafeCast.toUint16(_newPercentage / 1e16); // 3. Set weighted timestamp // i) For new _account, set up weighted timestamp @@ -479,11 +444,13 @@ abstract contract GamifiedToken is } // ii) For previous minters, recalculate time held // Calc new weighted timestamp - uint256 secondsHeld = (block.timestamp - oldBalance.weightedTimestamp) * oldBalance.raw; - uint256 newWeightedTs = secondsHeld / (oldBalance.raw + (_rawAmount / 2)); - _balances[_account].weightedTimestamp = SafeCast.toUint32(block.timestamp - newWeightedTs); + uint256 oldWeighredSecondsHeld = (block.timestamp - oldBalance.weightedTimestamp) * + oldBalance.raw; + uint256 newSecondsHeld = oldWeighredSecondsHeld / (oldBalance.raw + (_rawAmount / 2)); + uint32 newWeightedTs = SafeCast.toUint32(block.timestamp - newSecondsHeld); + _balances[_account].weightedTimestamp = newWeightedTs; - uint16 timeMultiplier = _timeMultiplier(SafeCast.toUint32(newWeightedTs)); + uint16 timeMultiplier = _timeMultiplier(newWeightedTs); _balances[_account].timeMultiplier = timeMultiplier; // 3. Update scaled balance @@ -501,11 +468,11 @@ abstract contract GamifiedToken is uint256 _rawAmount, uint128 _cooldownPercentage ) internal virtual updateReward(_account) { - require(_account != address(0), "ERC20: burn from the zero address"); + require(_account != address(0), "ERC20: burn from zero address"); // 1. Get and update current balance (Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account); - require(oldBalance.raw >= _rawAmount, "ERC20: burn amount exceeds balance"); + require(oldBalance.raw >= _rawAmount, "ERC20: burn amount > balance"); unchecked { _balances[_account].raw = oldBalance.raw - SafeCast.toUint128(_rawAmount); } @@ -519,14 +486,15 @@ abstract contract GamifiedToken is uint256 secondsHeld = (block.timestamp - oldBalance.weightedTimestamp) * (oldBalance.raw - (_rawAmount / 4)); // newWeightedTs = 875 / 100 = 87.5 - uint256 newWeightedTs = secondsHeld / oldBalance.raw; - _balances[_account].weightedTimestamp = SafeCast.toUint32(block.timestamp - newWeightedTs); + uint256 newSecondsHeld = secondsHeld / oldBalance.raw; + uint32 newWeightedTs = SafeCast.toUint32(block.timestamp - newSecondsHeld); + _balances[_account].weightedTimestamp = newWeightedTs; - uint16 timeMultiplier = _timeMultiplier(SafeCast.toUint32(newWeightedTs)); + uint16 timeMultiplier = _timeMultiplier(newWeightedTs); _balances[_account].timeMultiplier = timeMultiplier; // 4. Update scaled balance - _burnScaled(_account, oldScaledBalance - _getBalance(_balances[_account])); + _settleScaledBalance(_account, oldScaledBalance); } /*************************************** diff --git a/contracts/governance/staking/GamifiedTokenStructs.sol b/contracts/governance/staking/GamifiedTokenStructs.sol index 24438f69..06a38046 100644 --- a/contracts/governance/staking/GamifiedTokenStructs.sol +++ b/contracts/governance/staking/GamifiedTokenStructs.sol @@ -18,16 +18,19 @@ struct Balance { /// shows if a user has entered their cooldown ready for a withdrawal. Can be used to slash voting balance uint16 cooldownMultiplier; } + /// @notice Quests can either give permanent rewards or only for the season enum QuestType { PERMANENT, SEASONAL } + /// @notice Quests can be turned off by the questMaster. All those who already completed remain enum QuestStatus { ACTIVE, EXPIRED } + struct Quest { /// Type of quest rewards QuestType model; diff --git a/contracts/governance/staking/GamifiedVotingToken.sol b/contracts/governance/staking/GamifiedVotingToken.sol index 23c22f86..8c288fba 100644 --- a/contracts/governance/staking/GamifiedVotingToken.sol +++ b/contracts/governance/staking/GamifiedVotingToken.sol @@ -155,7 +155,7 @@ abstract contract GamifiedVotingToken is Initializable, GamifiedToken { // During the loop, the index of the wanted checkpoint remains in the range [low, high). // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant. // - If the middle checkpoint is after `blockNumber`, we look in [low, mid) - // - If the middle checkpoint is before `blockNumber`, we look in [mid+1, high) + // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high) // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not // out of bounds (in which case we're looking too far in the past and the result is 0). // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is diff --git a/contracts/governance/staking/StakedToken.sol b/contracts/governance/staking/StakedToken.sol index e71b596e..37698538 100644 --- a/contracts/governance/staking/StakedToken.sol +++ b/contracts/governance/staking/StakedToken.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.6; pragma abicoder v2; +import "hardhat/console.sol"; + import { IStakedToken } from "./interfaces/IStakedToken.sol"; import { GamifiedVotingToken } from "./GamifiedVotingToken.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -36,6 +38,8 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { uint256 public immutable UNSTAKE_WINDOW; /// @notice A week uint256 private constant ONE_WEEK = 7 days; + /// @notice cooldown percentage scale where 100% = 1e18. 1% = 1e16 + uint256 public constant COOLDOWN_PERCENTAGE_SCALE = 1e18; struct SafetyData { /// Percentage of collateralisation where 100% = 1e18 @@ -50,9 +54,10 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { struct CooldownData { /// Time at which the relative cooldown began uint128 timestamp; - /// Percentage of a users funds up for cooldown + /// Percentage of a users funds up for cooldown where 100% = 1e18 uint128 percentage; } + /// @notice Tracks the cooldowns for all users mapping(address => CooldownData) public stakersCooldowns; /// @notice Whitelisted smart contract integrations @@ -60,7 +65,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { event Staked(address indexed user, uint256 amount, address delegatee); event Withdraw(address indexed user, address indexed to, uint256 amount); - event Cooldown(address indexed user); + event Cooldown(address indexed user, uint256 percentage); event CooldownExited(address indexed user); event SlashRateChanged(uint256 newRate); event Recollateralised(); @@ -110,10 +115,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { * @dev Only the recollateralisation module, as specified in the mStable Nexus, can execute this */ modifier onlyRecollateralisationModule() { - require( - _msgSender() == _recollateraliser(), - "Only the Recollateralisation Module can call" - ); + require(_msgSender() == _recollateraliser(), "Only Recollateralisation Module"); _; } @@ -121,25 +123,30 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { * @dev This protects against fn's being called after a recollateralisation event, when the contract is essentially finished */ modifier onlyBeforeRecollateralisation() { - require( - safetyData.collateralisationRatio == 1e18, - "Function can only be called while fully collateralised" - ); + _onlyBeforeRecollateralisation(); _; } + function _onlyBeforeRecollateralisation() internal view { + require(safetyData.collateralisationRatio == 1e18, "Only while fully collateralised"); + } + /** * @dev Only whitelisted contracts can call core fns. mStable governors can whitelist and de-whitelist wrappers. * Access may be given to yield optimisers to boost rewards, but creating unlimited and ungoverned wrappers is unadvised. */ modifier assertNotContract() { + _assertNotContract(); + _; + } + + function _assertNotContract() internal view { if (_msgSender() != tx.origin) { require( whitelistedWrappers[_msgSender()], "Transactions from non-whitelisted smart contracts not allowed" ); } - _; } /*************************************** @@ -159,7 +166,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { * @dev Stake an `_amount` of STAKED_TOKEN in the system. This amount is added to the users stake and * boosts their voting power. * @param _amount Units of STAKED_TOKEN to stake - * @param _exitCooldown Bool signalling whether to take this opportunity to cancel any outstanding lockdown and + * @param _exitCooldown Bool signalling whether to take this opportunity to end any outstanding cooldown and * return the user back to their full voting power */ function stake(uint256 _amount, bool _exitCooldown) external { @@ -205,7 +212,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { address _delegatee, bool _exitCooldown ) internal { - IERC20(STAKED_TOKEN).safeTransferFrom(_msgSender(), address(this), _amount); + STAKED_TOKEN.safeTransferFrom(_msgSender(), address(this), _amount); _settleStake(_amount, _delegatee, _exitCooldown); } @@ -214,7 +221,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { * NOTE - Assumes tokens have already been transferred * @param _amount Units of STAKED_TOKEN to stake * @param _delegatee Address of the user to whom the sender would like to delegate their voting power - * @param _exitCooldown Bool signalling whether to take this opportunity to cancel any outstanding lockdown and + * @param _exitCooldown Bool signalling whether to take this opportunity to end any outstanding cooldown and * return the user back to their full voting power */ function _settleStake( @@ -231,23 +238,27 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { // 2. Deal with cooldown // If a user is currently in a cooldown period, re-calculate their cooldown timestamp - uint256 nextCooldown = getNextCooldownTimestamp( - _amount, - _msgSender(), - balanceOf(_msgSender()) - ); + CooldownData memory oldCooldown = stakersCooldowns[_msgSender()]; // If we have missed the unstake window, or the user has chosen to exit the cooldown, // then reset the timestamp to 0 bool exitCooldown = _exitCooldown || - block.timestamp > (nextCooldown + COOLDOWN_SECONDS + UNSTAKE_WINDOW); + block.timestamp > (oldCooldown.timestamp + COOLDOWN_SECONDS + UNSTAKE_WINDOW); + uint256 newPercentage = 0; if (exitCooldown) { stakersCooldowns[_msgSender()] = CooldownData(0, 0); + emit CooldownExited(_msgSender()); } else { - stakersCooldowns[_msgSender()].timestamp = SafeCast.toUint128(nextCooldown); + // Set new percentage so amount being cooled is the same as before the this stake. + // new percentage = old percentage * old staked balance / (old staked balance + new staked amount) + uint256 stakedAmountOld = uint256(_balances[_msgSender()].raw); + newPercentage = + (oldCooldown.percentage * stakedAmountOld) / + (stakedAmountOld + _amount); + stakersCooldowns[_msgSender()].percentage = SafeCast.toUint128(newPercentage); } // 3. Settle the stake by depositing the STAKED_TOKEN and minting voting power - _mintRaw(_msgSender(), _amount, exitCooldown); + _mintRaw(_msgSender(), _amount, newPercentage); emit Staked(_msgSender(), _amount, _delegatee); } @@ -290,7 +301,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { // 1. If recollateralisation has occured, the contract is finished and we can skip all checks _burnRaw(_msgSender(), _amount, 0); // 2. Return a proportionate amount of tokens, based on the collateralisation ratio - IERC20(STAKED_TOKEN).safeTransfer( + STAKED_TOKEN.safeTransfer( _recipient, (_amount * safetyData.collateralisationRatio) / 1e18 ); @@ -315,14 +326,14 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { uint256 feeRate = calcRedemptionFeeRate(balance.weightedTimestamp); // fee = amount * 1e18 / feeRate // totalAmount = amount + fee - // fee = amount * (1e18 - feeRate) / 1e18 uint256 totalWithdraw = _amountIncludesFee ? _amount : (_amount * (1e18 + feeRate)) / 1e18; uint256 userWithdrawal = (totalWithdraw * 1e18) / (1e18 + feeRate); // Check for percentage withdrawal - uint256 maxWithdrawal = (balance.raw * cooldown.percentage) / 1e18; + uint256 maxWithdrawal = (uint256(balance.raw) * cooldown.percentage) / + COOLDOWN_PERCENTAGE_SCALE; require(totalWithdraw <= maxWithdrawal, "Exceeds max withdrawal"); // 4. Exit cooldown if the user has specified, or if they have withdrawn everything @@ -335,8 +346,11 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { // e.g. stake 1000 and have 50% cooldown percentage. Withdraw 400 uses 40% of total // (500e18-400e18) * 1e18 / (1000e18 - 400e18) = 100e18 / 600e18 = 16e16 (16% of new total allowance) cooldownPercentage = SafeCast.toUint128( - ((maxWithdrawal - totalWithdraw) * 1e18) / (balance.raw - totalWithdraw) + ((maxWithdrawal - totalWithdraw) * COOLDOWN_PERCENTAGE_SCALE) / + (uint256(balance.raw) - totalWithdraw) ); + console.log("max, total, user"); + console.log(maxWithdrawal, totalWithdraw, userWithdrawal); stakersCooldowns[_msgSender()].percentage = cooldownPercentage; } @@ -345,7 +359,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { // Log any redemption fee to the rewards contract _notifyAdditionalReward(totalWithdraw - userWithdrawal); // Finally transfer tokens back to recipient - IERC20(STAKED_TOKEN).safeTransfer(_recipient, userWithdrawal); + STAKED_TOKEN.safeTransfer(_recipient, userWithdrawal); emit Withdraw(_msgSender(), _recipient, _amount); } @@ -383,7 +397,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { **/ function _startCooldown(uint256 _percentage) internal { require(balanceOf(_msgSender()) != 0, "INVALID_BALANCE_ON_COOLDOWN"); - require(_percentage > 0 && _percentage <= 1e18, "Invalid percentage"); + require(_percentage > 0 && _percentage <= COOLDOWN_PERCENTAGE_SCALE, "Invalid percentage"); stakersCooldowns[_msgSender()] = CooldownData({ timestamp: SafeCast.toUint128(block.timestamp), @@ -391,7 +405,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { }); _enterCooldownPeriod(_msgSender(), _percentage); - emit Cooldown(_msgSender()); + emit Cooldown(_msgSender(), _percentage); } /*************************************** @@ -412,8 +426,8 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { // 1. Change collateralisation rate safetyData.collateralisationRatio = 1e18 - safetyData.slashingPercentage; // 2. Take slashing percentage - uint256 balance = IERC20(STAKED_TOKEN).balanceOf(address(this)); - IERC20(STAKED_TOKEN).transfer( + uint256 balance = STAKED_TOKEN.balanceOf(address(this)); + STAKED_TOKEN.safeTransfer( _recollateraliser(), (balance * safetyData.slashingPercentage) / 1e18 ); @@ -432,7 +446,7 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { onlyBeforeRecollateralisation { require(safetyData.collateralisationRatio == 1e18, "Process already begun"); - require(_newRate <= 5e18, "Cannot exceed 50%"); + require(_newRate <= 5e17, "Cannot exceed 50%"); safetyData.slashingPercentage = SafeCast.toUint128(_newRate); @@ -534,38 +548,5 @@ contract StakedToken is IStakedToken, GamifiedVotingToken { } } - /** - * @notice Resets the cooldown start date taking into account what has already cooled. - * If cooldown has nearly finished and the new staked amount is relatively small, - * then the cooldown start date only moves forward a small amount of time. - * - * If cooldown has only just started and the new staked amount is relatively large, - * then the cooldown start date moves forward nearly a week. - * - * If staker is not in a cooldown period, return 0. - * - * @param _stakedAmountToReceive amount of new rewards being staked. - * @param _staker Address of the staker depositing rewards. - * @param _stakedAmountOld balance of staked amount before new amount is added. - * @return nextCooldownTimestamp new cooldown start timestamp or 0. - **/ - function getNextCooldownTimestamp( - uint256 _stakedAmountToReceive, - address _staker, - uint256 _stakedAmountOld - ) public view returns (uint256 nextCooldownTimestamp) { - uint256 oldCooldownTimestamp = stakersCooldowns[_staker].timestamp; - uint256 minimalValidCooldownTimestamp = block.timestamp - COOLDOWN_SECONDS - UNSTAKE_WINDOW; - - // If user has started cooldown and it has not already expired - if (oldCooldownTimestamp >= minimalValidCooldownTimestamp) { - // next cooldown = current time - (time already cooled * old staked amount / (old staked amount + new amount being staked)) - uint256 secondsAlreadyCooled = block.timestamp - oldCooldownTimestamp; - uint256 weightedSecondsAlreadyCooled = (secondsAlreadyCooled * _stakedAmountOld) / - (_stakedAmountOld + _stakedAmountToReceive); - nextCooldownTimestamp = block.timestamp - weightedSecondsAlreadyCooled; - } - } - uint256[47] private __gap; } diff --git a/contracts/governance/staking/StakedTokenBPT.sol b/contracts/governance/staking/StakedTokenBPT.sol index 2ffac319..f5490e82 100644 --- a/contracts/governance/staking/StakedTokenBPT.sol +++ b/contracts/governance/staking/StakedTokenBPT.sol @@ -50,7 +50,7 @@ contract StakedTokenBPT is StakedToken { */ function claimBal() external { uint256 balance = BAL.balanceOf(address(this)); - BAL.transfer(balRecipient, balance); + BAL.safeTransfer(balRecipient, balance); emit BalClaimed(); } diff --git a/contracts/governance/staking/deps/SignatureVerifier.sol b/contracts/governance/staking/deps/SignatureVerifier.sol index d9f907a0..303dc2c7 100644 --- a/contracts/governance/staking/deps/SignatureVerifier.sol +++ b/contracts/governance/staking/deps/SignatureVerifier.sol @@ -24,22 +24,18 @@ pragma solidity ^0.8.0; -contract SignatureVerifier { - address public immutable _signer; - - constructor(address signer) { - _signer = signer; - } +library SignatureVerifier { function verify( + address signer, address account, uint256 id, bytes memory signature - ) public view returns (bool) { + ) public pure returns (bool) { bytes32 messageHash = getMessageHash(account, id); bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); - return recoverSigner(ethSignedMessageHash, signature) == _signer; + return recoverSigner(ethSignedMessageHash, signature) == signer; } function getMessageHash(address account, uint256 id) public pure returns (bytes32) { diff --git a/contracts/peripheral/ENS/EnsEthRegistrarController.json b/contracts/peripheral/ENS/EnsEthRegistrarController.json new file mode 100644 index 00000000..60311fb8 --- /dev/null +++ b/contracts/peripheral/ENS/EnsEthRegistrarController.json @@ -0,0 +1,271 @@ +[ + { + "inputs": [ + { "internalType": "contract BaseRegistrar", "name": "_base", "type": "address" }, + { "internalType": "contract PriceOracle", "name": "_prices", "type": "address" }, + { "internalType": "uint256", "name": "_minCommitmentAge", "type": "uint256" }, + { "internalType": "uint256", "name": "_maxCommitmentAge", "type": "uint256" } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "string", "name": "name", "type": "string" }, + { "indexed": true, "internalType": "bytes32", "name": "label", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "cost", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "expires", "type": "uint256" } + ], + "name": "NameRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "string", "name": "name", "type": "string" }, + { "indexed": true, "internalType": "bytes32", "name": "label", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "cost", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "expires", "type": "uint256" } + ], + "name": "NameRenewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "oracle", "type": "address" }], + "name": "NewPriceOracle", + "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" + }, + { + "constant": true, + "inputs": [], + "name": "MIN_REGISTRATION_DURATION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "string", "name": "name", "type": "string" }], + "name": "available", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "bytes32", "name": "commitment", "type": "bytes32" }], + "name": "commit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "commitments", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isOwner", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "bytes32", "name": "secret", "type": "bytes32" } + ], + "name": "makeCommitment", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "bytes32", "name": "secret", "type": "bytes32" }, + { "internalType": "address", "name": "resolver", "type": "address" }, + { "internalType": "address", "name": "addr", "type": "address" } + ], + "name": "makeCommitmentWithConfig", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxCommitmentAge", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCommitmentAge", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "uint256", "name": "duration", "type": "uint256" }, + { "internalType": "bytes32", "name": "secret", "type": "bytes32" } + ], + "name": "register", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "uint256", "name": "duration", "type": "uint256" }, + { "internalType": "bytes32", "name": "secret", "type": "bytes32" }, + { "internalType": "address", "name": "resolver", "type": "address" }, + { "internalType": "address", "name": "addr", "type": "address" } + ], + "name": "registerWithConfig", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "uint256", "name": "duration", "type": "uint256" } + ], + "name": "renew", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "uint256", "name": "duration", "type": "uint256" } + ], + "name": "rentPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "uint256", "name": "_minCommitmentAge", "type": "uint256" }, + { "internalType": "uint256", "name": "_maxCommitmentAge", "type": "uint256" } + ], + "name": "setCommitmentAges", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "contract PriceOracle", "name": "_prices", "type": "address" }], + "name": "setPriceOracle", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "string", "name": "name", "type": "string" }], + "name": "valid", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/contracts/rewards/staking/HeadlessStakingRewards.sol b/contracts/rewards/staking/HeadlessStakingRewards.sol index d54d4071..f936af08 100644 --- a/contracts/rewards/staking/HeadlessStakingRewards.sol +++ b/contracts/rewards/staking/HeadlessStakingRewards.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.6; // Internal import { InitializableRewardsDistributionRecipient } from "../InitializableRewardsDistributionRecipient.sol"; import { StableMath } from "../../shared/StableMath.sol"; -import { PlatformTokenVendor } from "./PlatformTokenVendor.sol"; +import { PlatformTokenVendorFactory } from "./PlatformTokenVendorFactory.sol"; import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; // Libs @@ -37,7 +37,7 @@ abstract contract HeadlessStakingRewards is uint256 public constant DURATION = 1 weeks; /// @notice contract that holds the platform tokens - PlatformTokenVendor public rewardTokenVendor; + address public rewardTokenVendor; struct Data { /// Timestamp for current period finish @@ -80,11 +80,16 @@ abstract contract HeadlessStakingRewards is */ function _initialize(address _rewardsDistributorArg) internal virtual override { InitializableRewardsDistributionRecipient._initialize(_rewardsDistributorArg); - rewardTokenVendor = new PlatformTokenVendor(REWARDS_TOKEN); + rewardTokenVendor = PlatformTokenVendorFactory.create(REWARDS_TOKEN); } /** @dev Updates the reward for a given address, before executing function */ modifier updateReward(address _account) { + _updateReward(_account); + _; + } + + function _updateReward(address _account) internal { // Setting of global vars (uint256 newRewardPerToken, uint256 lastApplicableTime) = _rewardPerToken(); // If statement protects against loss in initialisation case @@ -99,7 +104,6 @@ abstract contract HeadlessStakingRewards is }); } } - _; } /*************************************** @@ -126,7 +130,7 @@ abstract contract HeadlessStakingRewards is uint128 reward = userData[_msgSender()].rewards; if (reward > 0) { userData[_msgSender()].rewards = 0; - REWARDS_TOKEN.safeTransferFrom(address(rewardTokenVendor), _to, reward); + REWARDS_TOKEN.safeTransferFrom(rewardTokenVendor, _to, reward); emit RewardPaid(_msgSender(), _to, reward); } _claimRewardHook(_msgSender()); @@ -245,7 +249,7 @@ abstract contract HeadlessStakingRewards is _reward += pendingAdditionalReward; pendingAdditionalReward = 0; if (_reward > 0) { - REWARDS_TOKEN.safeTransfer(address(rewardTokenVendor), _reward); + REWARDS_TOKEN.safeTransfer(rewardTokenVendor, _reward); } // If previous period over, reset rewardRate diff --git a/contracts/rewards/staking/PlatformTokenVendor.sol b/contracts/rewards/staking/PlatformTokenVendor.sol index 4f31c342..0cb20033 100644 --- a/contracts/rewards/staking/PlatformTokenVendor.sol +++ b/contracts/rewards/staking/PlatformTokenVendor.sol @@ -15,7 +15,7 @@ contract PlatformTokenVendor { address public immutable parentStakingContract; /** @dev Simple constructor that stores the parent address */ - constructor(IERC20 _platformToken) public { + constructor(IERC20 _platformToken) { parentStakingContract = msg.sender; platformToken = _platformToken; MassetHelpers.safeInfiniteApprove(address(_platformToken), msg.sender); diff --git a/contracts/rewards/staking/PlatformTokenVendorFactory.sol b/contracts/rewards/staking/PlatformTokenVendorFactory.sol new file mode 100644 index 00000000..a3997c97 --- /dev/null +++ b/contracts/rewards/staking/PlatformTokenVendorFactory.sol @@ -0,0 +1,29 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity 0.8.6; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { PlatformTokenVendor } from "./PlatformTokenVendor.sol"; + +/** + * @title PlatformTokenVendorFactory + * @author mStable + * @notice Library that deploys a PlatformTokenVendor contract which holds rewards tokens + * @dev Used to reduce the byte size of the contracts that need to deploy a PlatformTokenVendor contract + */ +library PlatformTokenVendorFactory { + /// @dev for some reason Typechain will not generate the types if the library only has the create function + function dummy() public pure returns (bool) { + return true; + } + + /** + * @notice Deploys a new PlatformTokenVendor contract + * @param _rewardsToken reward or platform rewards token. eg MTA or WMATIC + * @return address of the deployed PlatformTokenVendor contract + */ + function create(IERC20 _rewardsToken) public returns (address) { + PlatformTokenVendor newPlatformTokenVendor = new PlatformTokenVendor(_rewardsToken); + return address(newPlatformTokenVendor); + } +} diff --git a/hardhat-fork.config.ts b/hardhat-fork.config.ts index a34eba8f..5ace200f 100644 --- a/hardhat-fork.config.ts +++ b/hardhat-fork.config.ts @@ -6,8 +6,6 @@ export default { ...hardhatConfig.networks, hardhat: { allowUnlimitedContractSize: false, - blockGasLimit: 15000000, - gasPrice: 20000000000, forking: { url: process.env.NODE_URL || "", }, diff --git a/hardhat.config.ts b/hardhat.config.ts index 249e2778..c0230553 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,19 +22,14 @@ import "tsconfig-paths/register" export const hardhatConfig = { networks: { hardhat: { - allowUnlimitedContractSize: true, + allowUnlimitedContractSize: false, + initialBaseFeePerGas: 0, }, localhost: { url: "http://localhost:7545" }, - fork: { - url: "http://localhost:7545", - }, // export the NODE_URL environment variable to use remote nodes like Alchemy or Infura. eg // export NODE_URL=https://eth-mainnet.alchemyapi.io/v2/yourApiKey - env: { url: process.env.NODE_URL || "" }, ropsten: { url: process.env.NODE_URL || "", - gasPrice: 30000000000, - gasLimit: 8000000, }, polygon_testnet: { url: process.env.NODE_URL || "https://rpc-mumbai.maticvigil.com", diff --git a/package.json b/package.json index 7d887943..2bd3edbc 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "@commitlint/cli": "^12.1.1", "@commitlint/config-conventional": "^12.1.1", "@types/lodash": "^4.14.138", - "@types/mocha": "^8.2.2", - "@types/node": "^15.12.4", + "@types/mocha": "^9.0.0", + "@types/node": "^16.6.0", "@typescript-eslint/eslint-plugin": "^4.14.0", "@typescript-eslint/eslint-plugin-tslint": "^4.14.0", "@typescript-eslint/parser": "^4.14.0", @@ -67,7 +67,7 @@ "hardhat-abi-exporter": "^2.2.1", "humanize-duration": "^3.21.0", "husky": "^6.0.0", - "mocha": "^9.0.1", + "mocha": "^9.0.3", "prettier": "^2.2.1", "prettier-plugin-solidity": "^1.0.0-beta.10", "pretty-quick": "^3.1.0", @@ -75,7 +75,7 @@ "solc": "0.8.6", "solhint": "^3.3.6", "ts-generator": "^0.1.1", - "typescript": "^4.3.4" + "typescript": "^4.3.5" }, "_moduleAliases": { "@utils": "dist/test-utils" @@ -91,19 +91,19 @@ "@openzeppelin/contracts-upgradeable": "4.2.0", "@tenderly/hardhat-tenderly": "^1.0.12", "@typechain/ethers-v5": "^7.0.1", - "@typechain/hardhat": "^2.1.0", + "@typechain/hardhat": "^2.3.0", "chai": "^4.3.4", "csv-parse": "^4.15.0", - "defender-relay-client": "^1.8.0", - "ethers": "^5.3.1", + "defender-relay-client": "^1.10.0", + "ethers": "^5.4.4", "ethers-multicall": "^0.2.0", "graphql": "^15.5.0", - "graphql-request": "^3.4.0", - "hardhat": "^2.4.0", + "graphql-request": "^3.5.0", + "hardhat": "^2.6.0", "hardhat-gas-reporter": "^1.0.1", "solidity-coverage": "0.7.16", - "ts-node": "^10.0.0", - "tsconfig-paths": "^3.9.0", - "typechain": "^5.1.0" + "ts-node": "^10.2.0", + "tsconfig-paths": "^3.10.1", + "typechain": "^5.1.2" } } diff --git a/tasks-fork.config.ts b/tasks-fork.config.ts index 893b84e9..81cce031 100644 --- a/tasks-fork.config.ts +++ b/tasks-fork.config.ts @@ -11,8 +11,10 @@ import "./tasks/mBTC" import "./tasks/mUSD" import "./tasks/ops" import "./tasks/poker" +import "./tasks/rewards" import "./tasks/SaveWrapper" import "./tasks/token" import "./tasks/weekly" +import "./tasks/ens" export default config diff --git a/tasks.config.ts b/tasks.config.ts index 6188ccb8..b7942dd1 100644 --- a/tasks.config.ts +++ b/tasks.config.ts @@ -15,5 +15,6 @@ import "./tasks/rewards" import "./tasks/SaveWrapper" import "./tasks/token" import "./tasks/weekly" +import "./tasks/ens" export default config diff --git a/tasks/balancer-mta-rewards/20200720.json b/tasks/balancer-mta-rewards/20210720.json similarity index 100% rename from tasks/balancer-mta-rewards/20200720.json rename to tasks/balancer-mta-rewards/20210720.json diff --git a/tasks/balancer-mta-rewards/20210728.json b/tasks/balancer-mta-rewards/20210728.json new file mode 100644 index 00000000..fa264cc5 --- /dev/null +++ b/tasks/balancer-mta-rewards/20210728.json @@ -0,0 +1,416 @@ +{ + "0x00329E92ED6fD0A0ef76DA9C476D51FaEB6B26a0":"393.189788870473", + "0x01222A114552e89E4C548170f8c1f4d5460D255A":"19.016142999762", + "0x014930C2d8878759763F3ac2A492FF94073FD80f":"2.245744832398", + "0x02c4A98CcDfcA694807554BEc1cFf46152b6d3d8":"0.020061558521", + "0x0351B0B81fDD1DB5dAd0617D97c598CD3C37BAAF":"3.432106587022", + "0x03886228BB749eEBA43426D2D6B70eba472F4876":"4.682859883194", + "0x03f938F62fbE032d3197b4F74fA4c228dE73088C":"1.074712987796", + "0x045C7Ea70B3Faec4B39b6f0C91e12cfd026ACbfd":"17.087210171693", + "0x05861850BDb054B967c9405C4d8BaeF2E047473a":"0.000707527782", + "0x05928B30CdadF923980ad00655f89349D08751fD":"15.965963086162", + "0x05B258373d15956a927C3a57C3005e9D37fC0949":"23.242441071473", + "0x0628bE35ADF5B97C90d15fdCB7bE30079b530Ff9":"0.000806871662", + "0x06F2E2976348A38e9e45B224C45AFB19764135Df":"0.913199855249", + "0x0714954310cF39878A43dE5f7f0cE65D5C904e23":"0.096798065973", + "0x074396D67D04c48fdbEf50af95df216185B63cD5":"7.754290316900", + "0x07619FBeC9132d12F81B8de9E6a82E6de2589765":"54.552510597333", + "0x07e8531e64Dc6Ad4e2d0903a4E3E98d5285b9EC9":"52.634663162939", + "0x07eFAFC95A0fB31a6216300182350712cD1aB333":"6.806452149073", + "0x0A8e20Ee171630EF9dfeBF02149169f90c133cd8":"67.754123319873", + "0x0AC2aF90Eb5Ab7ec9901e81500f3045D4273e68E":"146.396980761773", + "0x0CB3649bd6d98fbfc3c9A9A3dEC61bB300a3A5a3":"0.156039821710", + "0x0E2353516EB6208aA84a552b9a1EE5f13eDaDa57":"8.822742359081", + "0x0a366592E4D38e10C320658CCadB4228197a51Cb":"3.718443092890", + "0x0b3437987D2ED94C58BFE42abD3Abe1f4d967864":"3.899835007293", + "0x0be5149120375A4B5ba59352D55173C9B0283C3d":"0.658064252315", + "0x0dB5570f3Bc313E99CDfa7fe77a6Cb87E1f3E85E":"0.004642671295", + "0x0e22b17cBc92CC9BFAf06E875dFaF1fDCA5D3715":"0.008085387477", + "0x0e54EDd4cF6568DE41fc67eaa71AEB52885999C7":"47.611282868747", + "0x0e61dae710688C22d8f6D0C3Fdd1735d27dDff8f":"7.747247344268", + "0x0ee0Ae881c5F70A6aC29858508fb62b54aBBa807":"0.015031676546", + "0x1002F408759C0bee0D4deeD7fbF1cC1071210f4D":"0.000482708549", + "0x10061123876CEeA12642342AfE829444b7E0d48a":"0.391152267442", + "0x114E562C33e9eC7a5c2cD9289ea37E476D38e70D":"0.074265916485", + "0x119460eCc4C538a02307e5D4f687BB83eDdC5cf9":"0.015700072131", + "0x125aD150043F25A8C0A09C8b82Ac2a26EaAC1400":"2.989263667504", + "0x12af0CBB90edEdCf871b07d3d9F41EC60E24D8d0":"0.749445176588", + "0x12b2946224Aa50Cc88Cd026B9bDC19631a96564e":"22.747960687480", + "0x131A09520cC82198F7b657E9A38A061b2Fc42DED":"0.007481974842", + "0x136200E3728fFdc1688e9dF19F0B6878422c0Fbf":"0.000340265646", + "0x136499A6afB3D429998674D54CF6965f710b88CC":"0.014981158963", + "0x140e7364eE4E29a677a9d6cCF26ac945e7E80B65":"1.338973115930", + "0x147FDCf537556D459fFb160a61297C8168507e81":"14.122689000646", + "0x150B43888b42d00E72f6Fc1278286cA6293c0325":"1.420931329611", + "0x15ba9D87aCe05ADF00d4B8fF17B3472d0A4052B0":"0.009673554002", + "0x188Eb6A325EA019C0c192542C77e0E19c4825f17":"10.765300861865", + "0x18e692e3cb8354B9539C4a1aF15bAd67A72e004C":"0.090578781870", + "0x1944CA723f77771B2b4EC10110494eff1fB5Cb36":"0.519349627654", + "0x1Bd8430199409206ee70e29932b04B41014104d9":"82.579841598447", + "0x1E313878498b9cD2c04f6594A2a288374953E93C":"18.190186800746", + "0x1EF454D9adA201CB841AdEc2f48944Ad918cD179":"0.232511092575", + "0x1F15B9414451B57e129f3D22C83B33Faa7fe35cC":"0.219839140922", + "0x1a0A64b458B4b70d5C360b164f7bc78ec4d80739":"3.390531985940", + "0x1abEd56b27691Ba3b5c0c68D2B0Be0f1638e3D4f":"1024.208175063406", + "0x1bf4D764FeBb13FE809DfA82F0d7ecD554FDC55f":"0.015001141948", + "0x1e470C6B3e874477Eb4eaDdF7D0F2c590794d920":"2.868838791727", + "0x1fAc6EEe2197c98D1ef0F166ECcFFA8632e9a279":"0.713908258815", + "0x1fa02E36cb086f2A89ADBa718C65DB7085d16756":"0.217966397318", + "0x209Da1FC73610881A80F1591668a34CC2B9da800":"0.376034012024", + "0x210aBF3Bb60261bc5870c00794Dee33CfF00374f":"3.179664564252", + "0x21777Dd9320C3B5D120C85C45d35968D771652C8":"6.203235598698", + "0x217c70A564a9BC0572426Fc068EfF0CaF70e6b41":"297.663891025405", + "0x21d79c2Be4ab3de2802a60Da66f86D497D06102B":"0.603048501109", + "0x238926025E84475e3182774df480021470f8F978":"181.037629753578", + "0x2441F46002F1f733cE4BDC62DCF9d5deA357414c":"3.453177069734", + "0x24437F22bC7Ee46Dbd9fedf73ed5934F71d118b5":"0.362507670657", + "0x24a30823bd87E785B0c4B3803a2bffD91eb6876F":"3.882761111788", + "0x24d1fEFf00F89cC6C40a845E11ed55bBed621822":"0.038764326310", + "0x2510467b222aE54496b118b43AfA046cF6fd59F6":"0.010670652013", + "0x25B70c8050B7e327Ce62CfD80A0C60cCcf057Fa6":"0.204557303923", + "0x26602a5f7eFF57FA32D9cfB08cAc447AaA93eD38":"0.000044805461", + "0x2701B5d0e417155E7a2B6D6DDfE7f016Ed94846E":"1.745609182392", + "0x2814c361BF1289cEf2958dB2fd9dE154f37a8963":"0.085118662960", + "0x2923Be587904F3034FFAB36C86c495Df3C21Bc97":"25.855609235693", + "0x2AA54f9C1fCF2b461347823d0b912E8548846FbB":"0.734226286942", + "0x2B83aC024B0a4Ba3f93776Cacb61D98310614aFE":"5.680768358824", + "0x2D2170D22d67D3eb3096FeB73b8100ba01d76E6a":"0.000119057125", + "0x2D39002e294e11b97C81389A6215437cBfAb4287":"0.504162310771", + "0x2a9b5fCa68b190b36c8DceB34DD6d97F74Fb3cb1":"7.591445527665", + "0x2bA5D050ba95bc4CBa690b0dd917A2984f6Eadfa":"0.005588872488", + "0x2dB50A4FbBE001608DCBdf8300cE5009A9A47F4e":"1982.684081579543", + "0x3210418872ca3C52798eFE4844A4F17A95662D9b":"130.754512045330", + "0x34319b1DeB14fb1618818F5ba54F2864Ba09D512":"0.132817178083", + "0x34cadF09dbf50a39D1a68b87D7763DB3961ef483":"36.354804783000", + "0x352F1b61ECc2A3ad9c1584BcB7cb2794e55A4a75":"16.646884091359", + "0x3724583Ad51c8f7c4aB168dDcD185681Db07Baa5":"0.173923383770", + "0x374FCF2358549EeE84b564ddfF9497e2887Eb0Ef":"57.025383063801", + "0x3A645aBbc55A6a77E7c424D7e64C289b00b6Fa30":"0.240355442947", + "0x3A8C3F087B7c908Cc4d4A0345ECD8C49d7f6CC5b":"0.224098458944", + "0x3B8752CaB4D90828EB847D4994237F36C5308285":"4.313837782476", + "0x3C918F4462C6e7CCE53450A6E619a22f7B4d42F6":"2.088873372230", + "0x3D3dC4B0445f1063644E9D0B2Ba1c66F7958f248":"18.318581515806", + "0x3DF1b295d394B6c77415e9659Cc1a7DBCcA76Be7":"5.557276838533", + "0x3E00ff6Eb846F1BAD74fc177186eBC7e45883ec2":"0.518493184337", + "0x3EF60aE2acE351dC6058B5B21Fb566089D118137":"0.000006105184", + "0x3F3B9B0F14DEF5817a61437CCB25CcBBD4853fFD":"877.559869583547", + "0x3Fc3E6514fD4925f55fB3Ae17bbfbca2eb126608":"0.576356622695", + "0x3c06601a9605d41a81df6E3f65208D82fcc411d8":"8.024503998880", + "0x3cd0D6d95B95D941FE0acDe2fE6C52E780D076dB":"0.445323414025", + "0x3fcAc584d0B71A9564602C1f0288eC1c0d9846cf":"2278.175817294299", + "0x40eb586F9bD55B1a05b6F840e0b43d4877494050":"4.694128169270", + "0x41546ACDE94953FAdE02CF27a25303a4159D5187":"4.176833990325", + "0x42640923a25A1aC5A925696CEee08a9bB4d7eB34":"0.006432268581", + "0x439D6dF49a7ACA63c014bCdBD047ed601C943447":"2.748042659429", + "0x43Ba3B417E914bED27DB8e16f4e9De0247Ba6597":"380.120319482103", + "0x467F5Ecf005293b92f59fDe6DD9aD16132471825":"0.663663016936", + "0x4783281A06E91E5D0A136be35b86BD93E8d43904":"73.361035162964", + "0x47945E744d4D1c4770E0e766c9c14619976ECB66":"55.919951235498", + "0x48A7BEA12A800957113662d27468fD1C9E8D42Aa":"218.800811867226", + "0x48c06dFB7a2245c288494f08FCC5b1D5A0312c62":"1.450125806816", + "0x497f24DA9a889C1dC9b8acbA00d5F2275B5dd166":"38.486627561845", + "0x4993BE2d24C2DD912D7B25922a652e8bB26F9a1d":"0.006472214260", + "0x4Bf1fBC233Cd6109c35a4cd9D34540173Af3234c":"1.832010503895", + "0x4C4baa9907F23c1C51ffF261B518e704Ce1d491d":"0.123678411252", + "0x4CA2d2E5b162335CCB893B0b6CBBA10565B54677":"8.380259066780", + "0x4E1eF49A194fd9527c5C8758c5795Eb28B147929":"3.419700092812", + "0x4FfD0A59a26cB2Aa76D403215e4cC2845C053994":"0.106340036259", + "0x4aE525227c761D9DfEfc4D7C268884151a118Db9":"0.000008396441", + "0x4b3f301699a0B12fD47306becB2db8f679bE674c":"0.003451337796", + "0x4b887546C27b2dF2E152fd2d2dD93030706D2363":"25.270603551901", + "0x4e954215a990fe55b7FeE2CA508E19c1DA0c8eaE":"41.695277022998", + "0x510d0E4DeF20c6BFd84f080BC424Bac5C66941f4":"0.871709616953", + "0x511aC1e6868d992c626ea3474B082e4264902FDA":"0.387764387251", + "0x526aD25f4cF234e429967c296698B963af813D31":"5.638176327029", + "0x52d604536344d86aCb67fac69b5f057874fAA3A2":"0.154926997571", + "0x530115e78F7BC2fE235666651f9113DB9cecE5A2":"0.076759322874", + "0x5337E7d65bbEF09Dc425441cf0929C1648b353b8":"0.889030186695", + "0x53E7bB9a05717F8cecE9399BBF9C908cd61BB64C":"0.174066493335", + "0x53d338f84A1aeB8a9cA997Bb8083Cb9CD8fb3CA1":"0.021354013743", + "0x545d68F8AC56bB941ec9122b2752Ca530e810c86":"3.497817757011", + "0x5461C09d7b61E4beFFCd782DC333f102ed7F6e24":"0.001347352110", + "0x5632760a43Bd3C09EC544Eb62EAe9cf0A0dB686e":"0.569616885811", + "0x56346a61158532177a33188FEC1954d495C4d330":"0.006382806243", + "0x567e21f97a3FcD97Fb53248E9658A7361A2830a5":"0.226776668421", + "0x575f436EB9ba16F4181DdFb21d13cDA0E8678EaD":"1.648858443030", + "0x58d6288EeaE05ECE61a2965F8003382a89395C78":"0.522449246976", + "0x58e6884f4C0c5f8114854eF7322b4cf03086F1fb":"0.499723109465", + "0x5999865518C9de250b820762130FF7A7119A4558":"0.871285403776", + "0x59D5D00d29e8B70cE0c7770DD98c1d6e8E4D2c61":"3.495336902285", + "0x5A0D6d0DE7c74899F09d3509A429bEb7D3b4b1d0":"1.050678030608", + "0x5C89C420A9E82Ea9AEDBaAab03302e39982919b9":"8.510219885076", + "0x5DD596C901987A2b28C38A9C1DfBf86fFFc15d77":"571.056563783454", + "0x5Eb5a01b3B18dEdC76f574A08368aF874cB53aF0":"11.247687573116", + "0x5Fa9B470E4F38f612b89dE9EFCC652B56d514833":"1.082250396196", + "0x5b7E09fD51A3981bA55e3CC2383ddE417405859d":"2.481815383185", + "0x5b954bee2845814cf38bD90F7d3F5f6E5f2C7078":"0.008342131046", + "0x5f7010C06296b593dc9B680Ce745eA2101700A5f":"1.603110030635", + "0x6015B208586DB5BFb398154AAdC411F46b96F485":"0.000071323233", + "0x602b0c3a8A91C61a3b36B3ce5ffcc88D5D7bdaDC":"0.080824297207", + "0x6146532Bb9ccA4bE0a087577877e2361f9FbB5c6":"46.454415918891", + "0x617E79672a4c1702878275f3e222ec22621789c6":"0.291908966076", + "0x63Dab7F4FC192C82559625E904F0C6102F580693":"3.705570691526", + "0x64f7354Fb18B7CeE35746078E1521B93feE6E061":"11.481592640183", + "0x651C636fC2adbB79fB2c8FBb1Cf3A6F76ff1fDd9":"0.066868562020", + "0x657A38e6994CB578351376dCDb077330D00665d6":"1.680648316091", + "0x6684942f6Bd9554adaD63A6DaDD6aA4996f1b591":"32.218885496034", + "0x67615b2cD47960E5598374A0dDd7ab1CdF559Dd5":"0.287325580381", + "0x67941779E59CEFDBc61Af9Cb047d44C173301795":"461.302748557825", + "0x6892fefDD8DC620FD645646f7AdDf3Fe72787492":"1.812641472608", + "0x696997e2293B519889ea790f14D18aBd104F5283":"13.507471351810", + "0x697F5736eE44454fD1Ab614d9fAB237BD1FDB25C":"0.112987539312", + "0x6A68B9CF65d64D060854f2cbB6825B6026BDe920":"1.565031095339", + "0x6D9A6049752A638beaA1733f5b3e6DaAdA862BB4":"2.002394908887", + "0x6F78aCdf39D0DE3487F6CDe929e9fdFAa541F8e5":"0.451248874308", + "0x6F9Cd1EB49CaeFa4044dDb4ba767B188C69C5a00":"9.540653350410", + "0x6FD8419608a996592e36D280fF948ef3d6A74C66":"2.537384763933", + "0x6cE699E7b3968fd238A99F2dccf104cBa99F9B9F":"0.128311252718", + "0x6f63945408c16531c755eEcEfCDB07C1312a3155":"0.607168082700", + "0x7019Be4E4eB74cA5F61224FeAf687d2b43998516":"14.857746260993", + "0x708C0F5A287EC8f8207e741024da27740ECD7119":"0.078147559267", + "0x70C9666B338795DdAb8f4bd67F580B1D9234b8Dc":"0.027088556738", + "0x713239442018ac24D3e7f3aE1FD726Ff7dc6B2A3":"0.009990425487", + "0x71BE7958a859b7A4f24D04C7E1a22c6B2026421a":"0.002476560764", + "0x72260dF0aA9BD4a5aBfE80B462C90b661f5576D8":"18.395285263241", + "0x726bDC1C6692fEcE418d449F4ffc36B81e0B4852":"22.401963001938", + "0x72c3C4B015f7C6C74e5443eeB08902AE0Fd4258A":"0.041841109225", + "0x73A651063F7E3169624c05d76F728D489a97dCD4":"0.705702643606", + "0x741Bc936B183F8bcF807C0204BeE62Fa68eA561f":"6.225258627576", + "0x74e3f6338d2ad2da1Be24B422eE7b00C4e46D9cB":"0.061783641714", + "0x75274AA765789B4b55eBE2fb22373865EFab4358":"0.145950491463", + "0x75547dbdE1370eE1Ff3128AdB5F28Fca196123De":"3.151604656857", + "0x77956E669727f8F081eBe6171e7d2E3ba832e099":"11.301668112813", + "0x77cB71Cf7421d690583c12a302fBB219EE87979D":"32.502734676997", + "0x7852d40F2a8Ea6C219A91aea2249f5a3F70D4dA0":"0.798546547062", + "0x78A0030Bb05c7CBbF8480001422AD87d64e8ed2D":"0.357146329751", + "0x7AbFce9bF06C6CAefDeB03b825C6F28cfF55aE90":"7.802196190115", + "0x7AfA9ebB50c63F5009C986c5ee6C7Ac60c052F48":"6.826172847724", + "0x7B716f389E382a05a2D84D41AC7866b1bceAee33":"0.505653600359", + "0x7C48a0C34E52C69Cc6A9d9b2e9e00D0A5D11E4dB":"0.048253376128", + "0x7Ca34EdAB943bDbe400B5450BA675dBa1d55FC20":"0.299064879917", + "0x7E24ac74A03Eb5bB60bDf2A3C9e142cD7DD36b7E":"15.861222602539", + "0x7a07F1Fc1d0DE73455A0aB0C3dEd66a34f5d985f":"0.149500352359", + "0x7aa7a54D58C8b2F2a8559301C880d3cEd10B7e55":"5.357277602200", + "0x7b0c025529F5E5f158fd1278415Eb156ED01E9D1":"104.514612845964", + "0x7bd82570bc838dEe6915B70059f554152C98A8b5":"0.437910432298", + "0x7bf623AD0f735c87cdD22D2781FE03C52CD082E0":"12.632410283656", + "0x7d50BD3e5837ebb68C3fb40f211031d66A38B184":"16.174034250305", + "0x7d935eA920F81c7d756F9Fce0A5639DCDb2E9839":"2.918765344625", + "0x7e98d97e0703a79299B65cB57a992a77a5079678":"15.642990354501", + "0x7f87f257e788c66DEE81F81D4091B245d4F57f52":"26.462451427856", + "0x813FFbF5C311cC7750cff0e241dBBE0dc01D3C51":"61.106029178051", + "0x81caBd4705fBe5701acd979DbEE416c6eEf31d4A":"1.549242492158", + "0x826ed9Fe9D60bc408d515BcFBC1E859b2d279b17":"13.008345855343", + "0x83c0ED34e4B8d2a197b79A7eA401b9a37e1A90bC":"10.218100317434", + "0x84a042Cc18d15aF0b5a1bA7CA77C634f5cEf07A0":"6.834469446558", + "0x852E45eD1a6502406Af4c409DF4e0Eef3CC0A0E3":"0.105669247953", + "0x862606a6012fA68019EE26ce4A3b35Bc2684BEb4":"115.396900043060", + "0x864d69e84BCBf88dc63c0333501B1db5D3fDBf28":"5.024374442429", + "0x86526f570cA6c2C9FFb4A535fFe50fA56fD9Dd87":"15.738614193409", + "0x86bDf1314dD3BF4d407dBAd7A2c43261916708d7":"1.588327724173", + "0x8740D9eC65b40bE5EbB84BD22607e81260fe3a3a":"29.402142691417", + "0x876B71aA426FAD5127910D3CC792e818B8229CF1":"1.501897847238", + "0x8776e4d197F5AeEc24765F3b2290443D001D07A6":"3.784914278180", + "0x877DDd15f1fC47E6336028013e15909Ec0C94249":"0.147489959396", + "0x87E62D5c9379C972f0A9D1411D8D2751CBcba27b":"4.559570124132", + "0x87fC1313880d579039aC48dB8B25428ed5F33C4a":"0.011374804443", + "0x8864FcD125E24d3cF52AAc71d0EDc61922566740":"2.006251617572", + "0x88DdD3103a8025B8C97A9eaBea22005716Cf5e63":"30.112597046760", + "0x8A47eE6008aB47174a90019413746309eF6D13DC":"0.560354900173", + "0x8B2856fBD13D98B30c7bd47114284Dffb7d223c9":"1.566188958056", + "0x8CBA226c4a94CDFe08f5711bB870459bc1abe0eb":"32.508115645221", + "0x8CbF96319b3C56d50a7C82EFb6d3c46bD6f889Ba":"0.000144142526", + "0x8D071fa6905E1fB872903603A6239947C03fd450":"0.048286942942", + "0x8DE3c3891268502F77DB7E876d727257DEc0F852":"0.007129701268", + "0x8cb3b23568877583Db17879CC1A5dcC397A328FC":"14.055628988044", + "0x8d6e4d3cD07342BF8006f962Bd8B67C752568334":"0.207669687892", + "0x8dA4b8ae83EA2Fcc7bdBAf9B6042B819CD2DaE3c":"0.069244774525", + "0x8e5aFAA162F224381195fBDC24787CAf1B4d24C8":"17.141200067811", + "0x90165030b38807b7764945ce5AE21Ee5B32eff74":"0.101824628933", + "0x90F15E09B8Fb5BC080B968170C638920Db3A3446":"0.006503636638", + "0x90aBCf1598ed3077861bCFb3B11EFcd1D7277223":"334.353054864648", + "0x926Dc3566F2563a8541968Ddd40180C9061FC06D":"0.416446558450", + "0x92cfE98Ea050A12d237eDb9b7901a15AaC88379A":"0.005240846881", + "0x931867863Be460131f305ebce2feEe83B839d5D4":"1.013442736853", + "0x9363080072da9510A469896E438A999a2C1Ca1f7":"2.035010998004", + "0x9397E20E9DAb0A814305Cf2953552C78A1E7bE0D":"0.395380784428", + "0x93b52eBcBa12cFE758f13285147dbc3b43a9896D":"386.254589853919", + "0x93b6F2f5Ac91596eFEC805acCAE0C04c00fe8882":"1.770732535776", + "0x943d616A6f0C973d93E08efa2501f935502B4B03":"5.921803811027", + "0x951b7393A03805573cFE0466682e676eb591834A":"15.342069416086", + "0x9582608416Ec5F854d2eD7e76600d175B5d23198":"18.785418468127", + "0x95F8D24cF78Dd25ce2FD9C210B18E58a972342a3":"3.106281181489", + "0x963eb0638A2FE075269b4E83F886e89d855ea4A8":"0.484191547785", + "0x97A29acC9E0E746f7c17BC545e8DE506db759ECd":"0.145526676136", + "0x97Ebf22E3eD3ff6573636D4aFEF82Ea0e4dA9127":"84.779170393477", + "0x9814C2c721244Af99dbcc314Bd9F53cE4DD905fE":"0.088060249881", + "0x99C0E6927F11845F13CC09c641748A4849203615":"0.690449555536", + "0x9C0790Eb0F96B16Ea1806e20B0D0E21A31DC93BC":"0.136093249933", + "0x9C21ad12A38b68259A545baCDc7B3BE42a580976":"2.294006094594", + "0x9D32F1e7C5fF707e92b4B30937A1e5E2C2De2462":"0.190228259537", + "0x9E8c8e6Af3d811ce281b0e6889c57e8b7A772786":"1.098504381031", + "0x9a141CF0507c1bE4380552FDB95C50C24b259ea3":"27.400745747909", + "0x9a4773EeEE73e34e1EE0E9A64E4b7453b0b04246":"7.031109796977", + "0x9cA8A0f39230D3a478B78617201A035c6d23Fbb0":"9.576377621751", + "0x9d653D68DA8d218Cd963bB051F17228Bd51Ea070":"0.616475505505", + "0x9fA10388D2BB8b7d8a2c8586772876D6302D4cB3":"7.232823418841", + "0xA07D83334758Af046DbCEe6991E341C0E061d121":"0.859288775090", + "0xA2ae59055e4B0e73A7e3F1A208062De2AEDee9be":"5.668497112329", + "0xA3dFB42724734D74d1B66f6F12a4AfA5D7D38DF2":"5.790890941365", + "0xA5F3B718A76b22659aC8cb34A301f557926619c8":"0.639096377254", + "0xABf28EF8fC76326f89765609310CCbFCca291d19":"0.005546707412", + "0xAE80890253a09F5196f9e0B843cF32dEF399C92c":"525.688580796826", + "0xAe053731E3b8705d3FB245D24d0300f6993a871B":"0.003857721222", + "0xB1EEF097cFEB4009Cebc3af3DAe3187D6ce57FFF":"52.624385706082", + "0xB2c7FE39cC240453CEA4cE03067d67b5fD049e20":"0.075368031710", + "0xB35E71b6A5aebB07dFc3aA705968411ebdBC003f":"0.758931002737", + "0xB5823C3b01b3CaB24206C6Eb0aE1BaE3fF33cA79":"13.821429813493", + "0xB58CEb3969C267ab823435D7b5690CE02c9617e4":"22.396775195097", + "0xB618D796bD7eD78387C7dE0e08BB9868e98fcDCb":"0.197820331872", + "0xB73Ba12b1Eec274162396D271C01D997e22f4E98":"0.554035000576", + "0xB7898CC9D5D398C9724d79CeEE09A498f8dA4c88":"0.012030679578", + "0xB8e34DcB781Bf6FA75b8d48d19c429638a98F284":"1.346498978825", + "0xB9542F9e7AB332346C818483bb8f3292a0653aB7":"0.290578052860", + "0xBF39bF63bDC221e4d2c41FEB5353F223031492c9":"0.002248209059", + "0xBFa0095523d7e2e76b30246a66Ec3481f488599e":"19.295102492532", + "0xBaec9fc6cCe5789fFd5b9831223658e16352b303":"0.808558810267", + "0xBd5f593C86319523e6E8AfE60dEdBEfaeDBdB8C9":"0.023685382187", + "0xBf97Af0d72Df8F15D40726A3f6618346395F3E66":"40.789851595818", + "0xC06dcd3F22a393ae29Ca3246FC01353742933378":"0.808823307780", + "0xC292EAFd5422A9c961CC2630CbDDf04c93Db33bD":"0.450704777326", + "0xC356E8fFEa6c866bE5F293d5FEe3A39c70e4075F":"38.678709889298", + "0xC5856751806C729d0D9ECc6953c6eD23ADD16D64":"0.091218088751", + "0xC5BCC6C6da4996f1351d2F51c9570B5F60821c8F":"0.037324802698", + "0xC6783Ef814540c0B6565982feBbc0FC5BCBDd1E6":"22.138049960536", + "0xC69aDcA4485C0C494f34e9Aa6b0A31f343f76411":"3.189416948101", + "0xC7F3A66f240B174cD4687854BC5cB90b592A58d2":"1.142555775731", + "0xC8b2227E1af2DB0f7d0E62975C716a6aD1805Fb8":"9.585694003023", + "0xC9420C9349120CA88D6C349CDF4Af038e2Ca5027":"62.844679724055", + "0xCB2A9c2CE7e3d060700d72Cfe5E6d2BB9959f2f4":"4.505704063949", + "0xCB4CFeC0a49Cd229909C54003930DD989806F176":"2.261746490519", + "0xCCC3ac360ffaf97fFd1fD274C60DA04DeBEbe099":"0.032572169014", + "0xCCE97ecbA396Ecd63433677fe7D38C01CC5acB8c":"0.040545415341", + "0xCD0e675a32ebcE86c78D1fE9B1E406899Ca7F8FD":"0.361569027234", + "0xCDb0884CdAB00353dBCF8874478d16d99Ae35A28":"7.429952276880", + "0xCcD1B2D70C4d1eFB25C527Ccce8cC8BDfe7c5Ea2":"0.287632040699", + "0xCd5eadBC411Aa119C00a6836cc680294be49F779":"0.602803104032", + "0xCeBba4E9f558716315FA543Ffcee29444484dC30":"27.257005977705", + "0xD004E55a84964d82341eE2ae58476de7D867Bc16":"0.486859712771", + "0xD0188348E4E57E0066385D634a6B568E23E1973e":"0.001513726452", + "0xD0d919780117d4F539887f393d3a9188d91A9022":"41.949233860644", + "0xD19a40c9ad255ac0ea9F771f0D6f08D0A84F1554":"4.428340012486", + "0xD1ccC963eF8119D935EdEB42e651f0cd81B83B77":"0.260884979555", + "0xD22581871a3f9908568124d9a50EAA1bC122117C":"3.817227437208", + "0xD663eB208f69Dd8e939327472FEddD3E9F58B34b":"634.643916769761", + "0xD6eC4E9aE5A6CFe888326f9Db9840C814a8bBd9e":"0.028567591479", + "0xDC2E88dD9bDF3Dc44636C7CCAC86AC6DbAE0a8b3":"7.561997744965", + "0xDb965BBAD97d0784Afc22A5D82a24c5478fAf7F4":"0.950243831209", + "0xDf7d7819649a25Ba46C62Dd89EB789a4Ec456aAA":"0.503213627919", + "0xE0850282Fa00b3F07D5f5D5837AA9bFFc2A69f4C":"0.000014284775", + "0xE0b458936fAa9BeeA9a8673372e25B63cD5E7543":"1.986129389597", + "0xE14a13b8eB93B6569a748EA57A1A97025fc82BE9":"0.000016212265", + "0xE1AB85E458ebEA43c5302CE84f5a89Cb22fE351a":"21.304178243329", + "0xE23756866b46A6666D3FF9febE3d19c3868f22E1":"25.972568433711", + "0xE45D85B382EFd7833Da1B8CAB53B203D22340b1a":"31.854610489169", + "0xE5A3D77D7788A83f3549dD7F08Fc2aCed7E262d2":"91.298206915357", + "0xE5FaCAEF11b03766cf44A9bf629F551FA2355E54":"53.966228571316", + "0xE60458f765bc61e78940c5A275e9523D1f049690":"106.556022545380", + "0xE6368DE4335F10D55d64BB879B7f3D151F8825Dd":"0.009335093523", + "0xE7294495206c1F5223D7F0C59B004Ce971760541":"0.175944106229", + "0xE76Be9C1e10910d6Bc6b63D8031729747910c2f6":"9.485874836400", + "0xE805Ff9b9bf7fbfd9EbE13379fC8E470025da0C7":"12.099339637339", + "0xEAC2C3d4f65CcE2Cb9197221934C04a4d1fEb863":"5.720847892023", + "0xEBAfEEeE4ae5275Fd6c064A589B8Bcdef4e554D1":"0.489997936899", + "0xEBf56B07485688Bf467e7C048EfE98922156b811":"12.229065826935", + "0xED3105a0eC578908FF55073A22DDa66943605e3F":"18.736647387871", + "0xEe2319094bBB69c3DF5f1d2E5Ef62fD2F20030a1":"2.526479109268", + "0xF02a31f977b52801322ec4891B1981CBAE182d3b":"0.007595130471", + "0xF02a5A6d2A16C7322f413059530FFAf2446AbC8f":"120.727353208700", + "0xF42bE2Aa2e0B455dF33164Df69d8e8B9c6308cC0":"0.002201608428", + "0xF502D3b098bBcd70CB865a450D3c4db009FF988B":"0.818343914520", + "0xF6Bedf12990D9D19C34b8c0bfe5835314AFE2bE5":"0.000450881832", + "0xF77302872064A27a50df43AD1f397A0082567dd6":"6.455616336749", + "0xF7B18e107eb36797f4cE36dE756630B9C30969ad":"22.989876265283", + "0xF913d7846fdf67CFe638988b239Ea76f2e081103":"0.000994722113", + "0xF99403A51BD63f1101967EA8087acd5f68986096":"1.430021496289", + "0xFBA206E73709F614E5a85afF27A98692d4F3c579":"22.307666557616", + "0xFE4F9b23A2Cfe38D022D27C18376bAb9256E023A":"9.255397864329", + "0xFF16d64179A02D6a56a1183A28f1D6293646E2dd":"17.402399657593", + "0xa184c0A1eA94B409672469C12d9ee0D1aA13aE37":"5.379462502631", + "0xa3FC7fa34aCAeAd9aE7d19622dF5E58850450564":"0.622230266990", + "0xa42c2bAB6AC560db8E59a9C5aA926166C4D4c582":"0.944179695492", + "0xa4507877aEa6fF504aBC99c256192e78F5124274":"24.752453447495", + "0xa5ed8DA239f400141427800DA33B602A039F2254":"0.003804023156", + "0xa6EB9c5B8Be18A1015EE6BF3698d927b982B1676":"0.066597630868", + "0xa6ab5ca03954E8B2bb54e9006efb8e68824271Fa":"36.625144515702", + "0xa6dfBEbf6F3Ae1C7c46E60A1d353387eDeCe70DF":"8.049903755243", + "0xa7AdC93cAA4c0e1E0461483EB6ec79C9D4e3AD82":"0.822388952036", + "0xa96704E2fb3Bf5Ca0e974baA4E1F5D938b2Ec934":"0.065079371009", + "0xa97E5Bff61454486b545538aCD574e664502B7ba":"0.056193494268", + "0xa9eAEF87c01B4C46a691862c7Ba94401394B8B9c":"0.032117195606", + "0xaB3219E1a492e14Ef7a35Da04D31Ed592bfC9bB4":"0.329768470768", + "0xaB43805bD19c5068F5C30bC4E5F187E2bB7258Be":"8.202165123623", + "0xaB96Cb3351582398616868f235958B068bce2881":"0.006612260212", + "0xaE7853A2AB2d63E2D9804A4Dd848A4C7f035eBFf":"0.147183690904", + "0xaa55b756Cc30EebB2728Fe5d43d334625e0A0b4c":"15.097549299693", + "0xaea8658914cf1EE552E30c8a71906cba5897b4AD":"189.520502947124", + "0xaf80E055534849d6fff0C9b178cc02c94bc1a36F":"3.997453233377", + "0xb04180e0d414E51e7a775944f1A6BA20BF955FB0":"3.777762066807", + "0xb0C6d7085fA8cCC6c174841495eb700130eA2e97":"0.373756601232", + "0xb0eF8441B79048C37CF3BF9645762673518a74E0":"1.373951595679", + "0xb181AF0c0Eaf08Bc3160Aeb653c867eeE87e68dd":"2.241960860798", + "0xb4633912c7374cd94D8917a167449e23b46d0AD1":"0.005241692092", + "0xb4aEED235917ac89CA283e9CAA51E8A1325cb62a":"21.657116680753", + "0xb4e7419A42EA01c418998FCc1df2ac0788e97566":"7.432021727921", + "0xb672246188E98faE363814E6d2566E3058F7ca46":"1.940134994664", + "0xbAC8075dB42624F0FC9d81E02942fF7c85f9A7D5":"0.263168138551", + "0xbC5f1521f3e1fe48a031e6c417Bb0fE61C28D7Eb":"3.123146049416", + "0xbCC5C7180155a055bF87d97531c4584D51074c8e":"10.527017452504", + "0xbF8FAA9A7bCa427317AC52a4fE020069bF317c08":"0.518607367740", + "0xbb3E24b2d16dcbb0DF7F663FDc8a83A914bBC017":"1.605133911076", + "0xbdF81b19af7848F7384c38E68208885ff0C9F390":"234.016947287744", + "0xc1919Db0c4D778693188c986462d351B2693Aa9a":"7.177274109459", + "0xc22c1e5998928f841be8731Bac18928A0F520bc4":"0.530969806889", + "0xc25fef376784E9BcaD3E1472575c1E10079c56d1":"12.500475860498", + "0xc4b2286bE2635A4CE1C986EDAb361f7391837cB7":"0.027956026292", + "0xc7B009237cB09690A4C887e9d4dc233f6F1e9e83":"7.542946290417", + "0xc865B4ECDbAB1d7a369d68b7627e1822468F680A":"5.918422492230", + "0xc875F257fae93BB9Eb664B8b51057299d1527BB4":"6.964962474132", + "0xc92B444E0BC428459FdA180d71058164fd5e0E10":"0.004568073668", + "0xc938421f4F283ab7886f172413B3ABa23988ae41":"0.040834189413", + "0xcAFA7a1Ac382B4127AC85E270dAA6DfA3175EB1a":"0.013024399064", + "0xcb47A044793227A943709B4F24BFcF6e01f1134C":"0.084369218522", + "0xcb59b8a261bBB6FfCb376b4c198BAA0bb896e6D6":"366.025226182224", + "0xcdeE3aaDeb1dAA89a415eB164737315026782af0":"2.859692630101", + "0xd1207f05eBE558D32057b3e569A436DF1F46Fa05":"5.604588300892", + "0xd14e9D40278B9ce3daA887414CD5772e847E2bfa":"0.185104614656", + "0xd185a341E2D30df901D4792F52af0265b45D7D9c":"37.301124555059", + "0xd300c4D11702F5E726590857250bcab5b927C7B7":"1.073519037420", + "0xd3c427D2b51854b8B21fEFEC0ca362454C23d365":"1.784912988609", + "0xd861415F6703ab50Ce101C7E6f6A80ada1FC2B1c":"0.304630653082", + "0xd9020D30AC868463c8B079430A2938263C43Fe90":"0.782479182781", + "0xd96A1769FdcB59DF2f5A18f91dDF6cCCd7002516":"0.026363985195", + "0xdEc79CEE4397202739ED948ef98846774FEb53f9":"7.595854489781", + "0xdb8b6CE8D269ff5Cf362D1F2787a9Ae57084b025":"0.000094497371", + "0xde7e8fB53E3Eba9D49cB0bFbB9A93AC63Aed07be":"5.393016880589", + "0xe07E487d5A5E1098BBb4d259DaC5EF83Ae273f4e":"31.937570222725", + "0xe76D4165967fB1ed8D5a56d5DEa87e3bc8ECd3AC":"0.025130236486", + "0xe7afdc19099d9FAB3e76b4297F5E7D3B597a7CFB":"0.368328114601", + "0xe8ec1272503a63b57A02dc808E4d41D2BBD22E9A":"3.952456077998", + "0xe919047aA85E80cC19e619a081Dc4dd5620803af":"0.278714337161", + "0xeA74bF7E0Bb562E2d97d11c5A2D94B0a900d03bD":"0.273270801780", + "0xeAa630449b584b7517318D046d78a4538c7E1FbA":"1.451215604195", + "0xeB312F4921aEbbE99faCaCFE92f22b942Cbd7599":"1.906511000575", + "0xeCE1477D3C350a42486D2DC802F6243E99409A41":"0.148061643821", + "0xebCFD08a0a9a389789CcB19Bd1ebBeAf91f4c134":"0.978984434340", + "0xeeb7c600722630Dd0b7698a23236E8465c1d2D79":"0.728567702558", + "0xf3Fbb2c0A711529eF9b81fE59a5Ef5b8f1E0eB27":"1.055016494714", + "0xf87809F81E30d59F7824066C19F74876f9254657":"1.121333224950", + "0xf8AFa751E397Ea4a1E80986cBfA8a1B6457499e0":"0.012339240339", + "0xfDd3631ad0eE38c3F49A1eaAcd2324e430e29Bb6":"1.395857212081", + "0xfE36dee625234EAEdbaDE27C15405eB482b81f1D":"0.088633938215", + "0xfa4a45D755eA1c2b72Dd581b3E05dde3bFc13fad":"1.052858270127", + "0xfb0A954fe9378b2418f59a94A4D41A3953F92cd8":"1.018926368719", + "0xfc25529b34482646527Abc46896c71dba15BcEAB":"3.146064552506", + "0xfe5660f2e5bA3012c4e517831d23EB0F97188bb5":"21.535757822527" +} \ No newline at end of file diff --git a/tasks/balancer-mta-rewards/20210803.json b/tasks/balancer-mta-rewards/20210803.json new file mode 100644 index 00000000..82daefb6 --- /dev/null +++ b/tasks/balancer-mta-rewards/20210803.json @@ -0,0 +1,487 @@ +{ + "0x00329E92ED6fD0A0ef76DA9C476D51FaEB6B26a0":"393.625320791547", + "0x01222A114552e89E4C548170f8c1f4d5460D255A":"19.037206968172", + "0x014930C2d8878759763F3ac2A492FF94073FD80f":"0.000004155448", + "0x02c4A98CcDfcA694807554BEc1cFf46152b6d3d8":"0.023366079242", + "0x0351B0B81fDD1DB5dAd0617D97c598CD3C37BAAF":"3.445498927102", + "0x03886228BB749eEBA43426D2D6B70eba472F4876":"15.751078606974", + "0x03f938F62fbE032d3197b4F74fA4c228dE73088C":"1.494576031878", + "0x045C7Ea70B3Faec4B39b6f0C91e12cfd026ACbfd":"17.236045494203", + "0x0553aeaaA787cA5BBCA6E172D3fDE33957ff4B91":"0.012730335080", + "0x05861850BDb054B967c9405C4d8BaeF2E047473a":"0.000451392479", + "0x05928B30CdadF923980ad00655f89349D08751fD":"16.646562823607", + "0x05B258373d15956a927C3a57C3005e9D37fC0949":"23.268186462876", + "0x0628bE35ADF5B97C90d15fdCB7bE30079b530Ff9":"0.000807765425", + "0x06F2E2976348A38e9e45B224C45AFB19764135Df":"1.196668340324", + "0x0714954310cF39878A43dE5f7f0cE65D5C904e23":"0.096905288105", + "0x074396D67D04c48fdbEf50af95df216185B63cD5":"7.762879657351", + "0x07619FBeC9132d12F81B8de9E6a82E6de2589765":"90.947865582107", + "0x07e8531e64Dc6Ad4e2d0903a4E3E98d5285b9EC9":"9.721631363674", + "0x07eFAFC95A0fB31a6216300182350712cD1aB333":"6.862509267141", + "0x0855f651Ed69B60A3104C7fd7e3FE6F8682Cf5D4":"0.007910495486", + "0x0A8e20Ee171630EF9dfeBF02149169f90c133cd8":"67.829173802681", + "0x0AC2aF90Eb5Ab7ec9901e81500f3045D4273e68E":"149.519691934089", + "0x0CB3649bd6d98fbfc3c9A9A3dEC61bB300a3A5a3":"0.156616663380", + "0x0D5Be16B4a2F17DAE8CE505D4fc37189798899A1":"6.280224605677", + "0x0E2353516EB6208aA84a552b9a1EE5f13eDaDa57":"9.199164140579", + "0x0a366592E4D38e10C320658CCadB4228197a51Cb":"3.722561970616", + "0x0b3437987D2ED94C58BFE42abD3Abe1f4d967864":"3.904154810809", + "0x0bF6a86F306d7a23d206C8C9880371C829408F9d":"0.018990504655", + "0x0be5149120375A4B5ba59352D55173C9B0283C3d":"0.641555688066", + "0x0dB5570f3Bc313E99CDfa7fe77a6Cb87E1f3E85E":"0.008491508404", + "0x0e22b17cBc92CC9BFAf06E875dFaF1fDCA5D3715":"0.008582890293", + "0x0e54EDd4cF6568DE41fc67eaa71AEB52885999C7":"47.951207617374", + "0x0e61dae710688C22d8f6D0C3Fdd1735d27dDff8f":"25.119915185488", + "0x0ee0Ae881c5F70A6aC29858508fb62b54aBBa807":"0.091797377498", + "0x1002F408759C0bee0D4deeD7fbF1cC1071210f4D":"0.003860306313", + "0x10061123876CEeA12642342AfE829444b7E0d48a":"0.423147461184", + "0x119460eCc4C538a02307e5D4f687BB83eDdC5cf9":"0.019101080532", + "0x125aD150043F25A8C0A09C8b82Ac2a26EaAC1400":"2.992574841356", + "0x12E007d8031004aD8AA582b8cf5ec0Daa7EEE9F1":"1.065594445395", + "0x12af0CBB90edEdCf871b07d3d9F41EC60E24D8d0":"0.750275328608", + "0x131A09520cC82198F7b657E9A38A061b2Fc42DED":"0.047006616137", + "0x133CBA6d89C4151f5B622a37Ef7F60F9FD9B5760":"9.998036427255", + "0x136499A6afB3D429998674D54CF6965f710b88CC":"0.039322525422", + "0x140e7364eE4E29a677a9d6cCF26ac945e7E80B65":"73.482070843545", + "0x147FDCf537556D459fFb160a61297C8168507e81":"14.138332544922", + "0x1495dD855f2F4C3A62492742cffE74f8BFbD42fc":"0.875084035655", + "0x150B43888b42d00E72f6Fc1278286cA6293c0325":"1.425939838583", + "0x15ba9D87aCe05ADF00d4B8fF17B3472d0A4052B0":"0.003372860054", + "0x1755840E9401E8e94A5Ae04B8fCDe3D63700ff47":"0.213780896963", + "0x188Eb6A325EA019C0c192542C77e0E19c4825f17":"10.777225464940", + "0x18e692e3cb8354B9539C4a1aF15bAd67A72e004C":"0.141836413198", + "0x18f768455E7f5fB09fC491fd86bcc282BcDD5973":"34.757608133527", + "0x1944CA723f77771B2b4EC10110494eff1fB5Cb36":"0.526103768307", + "0x1Bd8430199409206ee70e29932b04B41014104d9":"96.528387912944", + "0x1Ce99932fD278E00911814dC4bd403e1293d8ED2":"0.214394885571", + "0x1D9880e072a3125f552960F2e433df1981A5d237":"0.211023073204", + "0x1E313878498b9cD2c04f6594A2a288374953E93C":"19.179027988627", + "0x1EF454D9adA201CB841AdEc2f48944Ad918cD179":"0.232768642506", + "0x1F15B9414451B57e129f3D22C83B33Faa7fe35cC":"0.308880636260", + "0x1F452f2beeA8C1105cd5363b18A0995367a87D97":"0.002665366218", + "0x1a0A64b458B4b70d5C360b164f7bc78ec4d80739":"1.048299207351", + "0x1abEd56b27691Ba3b5c0c68D2B0Be0f1638e3D4f":"1025.609098205428", + "0x1bf4D764FeBb13FE809DfA82F0d7ecD554FDC55f":"0.015107968741", + "0x1dbbf91668C2f66169089f778bBDEB5697Fee004":"0.104156936955", + "0x1dc1B489D1dd16402337fb1FE3c76b4c37Fa651b":"0.429993921014", + "0x1e470C6B3e874477Eb4eaDdF7D0F2c590794d920":"80.304521707756", + "0x1eF21cBC4a40Ea0dc999c8013db556d7cEB5ae05":"0.711508991385", + "0x1fAc6EEe2197c98D1ef0F166ECcFFA8632e9a279":"0.286905009045", + "0x1fa02E36cb086f2A89ADBa718C65DB7085d16756":"0.168719765237", + "0x209Da1FC73610881A80F1591668a34CC2B9da800":"0.376450540683", + "0x210aBF3Bb60261bc5870c00794Dee33CfF00374f":"2.590736847757", + "0x2115E324da67FBee40c6A35563175a8CE6dD0554":"0.427556676809", + "0x21777Dd9320C3B5D120C85C45d35968D771652C8":"4.668625886923", + "0x21d79c2Be4ab3de2802a60Da66f86D497D06102B":"0.603716491173", + "0x2441F46002F1f733cE4BDC62DCF9d5deA357414c":"3.441873169498", + "0x24437F22bC7Ee46Dbd9fedf73ed5934F71d118b5":"0.367412152522", + "0x2486C40f5cBac257217129d681d5A4412BE7328d":"3.977172006501", + "0x24a30823bd87E785B0c4B3803a2bffD91eb6876F":"3.921834446367", + "0x25B70c8050B7e327Ce62CfD80A0C60cCcf057Fa6":"1.250865333759", + "0x26602a5f7eFF57FA32D9cfB08cAc447AaA93eD38":"0.181524691929", + "0x26bBec292e5080ecFD36F38FF1619FF35826b113":"13.324747448892", + "0x2701B5d0e417155E7a2B6D6DDfE7f016Ed94846E":"3.073625450664", + "0x27633D72f0611C2F84f6CA9467fA0f50B4eee59E":"0.266138249333", + "0x2814c361BF1289cEf2958dB2fd9dE154f37a8963":"0.183784011159", + "0x288a2395f027F65684D836754bA43Afa20CA09e6":"3.034300804919", + "0x2923Be587904F3034FFAB36C86c495Df3C21Bc97":"25.975179152636", + "0x2AA54f9C1fCF2b461347823d0b912E8548846FbB":"0.735039581168", + "0x2D2170D22d67D3eb3096FeB73b8100ba01d76E6a":"0.000773034490", + "0x2D39002e294e11b97C81389A6215437cBfAb4287":"2.168673264366", + "0x2D782B4e65db9F5545774801494b48047897d060":"0.057827791501", + "0x2a9b5fCa68b190b36c8DceB34DD6d97F74Fb3cb1":"7.599854486768", + "0x2dB50A4FbBE001608DCBdf8300cE5009A9A47F4e":"1995.745868822227", + "0x2f2AE44e7eD861A4569b50eb57642E5e75d49EB3":"0.596124746169", + "0x3210418872ca3C52798eFE4844A4F17A95662D9b":"139.676191771683", + "0x34319b1DeB14fb1618818F5ba54F2864Ba09D512":"0.910101159998", + "0x34cadF09dbf50a39D1a68b87D7763DB3961ef483":"36.395074592684", + "0x34cd8a21E92b0Abd558Ff02D6cc7a9e12DAf0ff1":"2.167317887025", + "0x352F1b61ECc2A3ad9c1584BcB7cb2794e55A4a75":"16.665323658238", + "0x3724583Ad51c8f7c4aB168dDcD185681Db07Baa5":"3.746724004060", + "0x37450907Ea70a6e9B60f6b27fFCe6056D4661628":"1.745023304917", + "0x374FCF2358549EeE84b564ddfF9497e2887Eb0Ef":"75.202681573017", + "0x381119796410Fe4b2896466C71A40C46a93Fd9E5":"0.010988119224", + "0x3A645aBbc55A6a77E7c424D7e64C289b00b6Fa30":"0.240621681977", + "0x3A8C3F087B7c908Cc4d4A0345ECD8C49d7f6CC5b":"0.224346690294", + "0x3B8752CaB4D90828EB847D4994237F36C5308285":"1.487200859534", + "0x3C918F4462C6e7CCE53450A6E619a22f7B4d42F6":"0.410690511699", + "0x3DF1b295d394B6c77415e9659Cc1a7DBCcA76Be7":"5.563432571784", + "0x3E00ff6Eb846F1BAD74fc177186eBC7e45883ec2":"0.653183511529", + "0x3F3B9B0F14DEF5817a61437CCB25CcBBD4853fFD":"904.669843458906", + "0x3Fc3E6514fD4925f55fB3Ae17bbfbca2eb126608":"0.579814060982", + "0x3a14Ef4E56cf355E0D6a08a478fD43b10704535e":"0.152893035061", + "0x3c06601a9605d41a81df6E3f65208D82fcc411d8":"5.800077350345", + "0x3cE6A3bC62D6F1427340128277ac43Fd617C2CaB":"0.015053604508", + "0x3cd0D6d95B95D941FE0acDe2fE6C52E780D076dB":"0.528771611958", + "0x3fcAc584d0B71A9564602C1f0288eC1c0d9846cf":"2280.699327106438", + "0x40eb586F9bD55B1a05b6F840e0b43d4877494050":"30.847220869096", + "0x41546ACDE94953FAdE02CF27a25303a4159D5187":"4.242947696583", + "0x439D6dF49a7ACA63c014bCdBD047ed601C943447":"3.592453560080", + "0x43B5217A1f0f5E2dcF8019Ff740f9252a4351701":"0.000516644954", + "0x43Ba3B417E914bED27DB8e16f4e9De0247Ba6597":"67.916213223380", + "0x467F5Ecf005293b92f59fDe6DD9aD16132471825":"0.682843320027", + "0x4783281A06E91E5D0A136be35b86BD93E8d43904":"73.442296359162", + "0x47945E744d4D1c4770E0e766c9c14619976ECB66":"109.009384917666", + "0x48A7BEA12A800957113662d27468fD1C9E8D42Aa":"186.852701576600", + "0x48c06dFB7a2245c288494f08FCC5b1D5A0312c62":"1.451732094915", + "0x491180FaC09C5A3f76e2Abc2dffaae4500b73Aa5":"0.155385243297", + "0x497f24DA9a889C1dC9b8acbA00d5F2275B5dd166":"38.529258767721", + "0x4Bf1fBC233Cd6109c35a4cd9D34540173Af3234c":"3.577128343746", + "0x4C4baa9907F23c1C51ffF261B518e704Ce1d491d":"1.013770579766", + "0x4CA2d2E5b162335CCB893B0b6CBBA10565B54677":"16.983527969831", + "0x4E1eF49A194fd9527c5C8758c5795Eb28B147929":"3.423488056266", + "0x4FfD0A59a26cB2Aa76D403215e4cC2845C053994":"0.143540398815", + "0x4aE525227c761D9DfEfc4D7C268884151a118Db9":"0.000018942745", + "0x4b887546C27b2dF2E152fd2d2dD93030706D2363":"25.222821588174", + "0x4c60651f30Db21aF8fD3c6a4c6033C6813C61501":"0.548332613124", + "0x4e954215a990fe55b7FeE2CA508E19c1DA0c8eaE":"34.211292284116", + "0x510d0E4DeF20c6BFd84f080BC424Bac5C66941f4":"11.982406877903", + "0x511aC1e6868d992c626ea3474B082e4264902FDA":"0.592850575364", + "0x526aD25f4cF234e429967c296698B963af813D31":"5.695911264355", + "0x52d604536344d86aCb67fac69b5f057874fAA3A2":"0.233877599366", + "0x530115e78F7BC2fE235666651f9113DB9cecE5A2":"0.076844348315", + "0x5337E7d65bbEF09Dc425441cf0929C1648b353b8":"1.448920688978", + "0x53E7bB9a05717F8cecE9399BBF9C908cd61BB64C":"0.486555829409", + "0x53d338f84A1aeB8a9cA997Bb8083Cb9CD8fb3CA1":"0.037456980165", + "0x545d68F8AC56bB941ec9122b2752Ca530e810c86":"31.467192090043", + "0x5461C09d7b61E4beFFCd782DC333f102ed7F6e24":"0.000091504868", + "0x5632760a43Bd3C09EC544Eb62EAe9cf0A0dB686e":"0.533598894840", + "0x56346a61158532177a33188FEC1954d495C4d330":"0.079738444970", + "0x567e21f97a3FcD97Fb53248E9658A7361A2830a5":"0.452945878063", + "0x574378e51d3973ce57Ce0F9a48C29FC06f4f003F":"17.732761311454", + "0x575f436EB9ba16F4181DdFb21d13cDA0E8678EaD":"1.658688507367", + "0x58d6288EeaE05ECE61a2965F8003382a89395C78":"0.523027958150", + "0x5999865518C9de250b820762130FF7A7119A4558":"0.872250516850", + "0x59D5D00d29e8B70cE0c7770DD98c1d6e8E4D2c61":"3.334342003649", + "0x5A0D6d0DE7c74899F09d3509A429bEb7D3b4b1d0":"6.119093626447", + "0x5C89C420A9E82Ea9AEDBaAab03302e39982919b9":"20.251626768630", + "0x5Dac9ccC215b9Af65B486066786F79d9aa0043Da":"3.383342357557", + "0x5Eb5a01b3B18dEdC76f574A08368aF874cB53aF0":"11.667890681594", + "0x5F2c54a59986BBc0736442BCBFA6C34EA5a25b3d":"0.004073551132", + "0x5F937a82389CA4C25bb0Ab36d5e1616aaC5d8C38":"0.753409031484", + "0x5Fa9B470E4F38f612b89dE9EFCC652B56d514833":"5.704897107104", + "0x5b954bee2845814cf38bD90F7d3F5f6E5f2C7078":"0.090271381540", + "0x5c10afD2E8C8aE7E282AEA60759209f32EdFe3d8":"0.480334902790", + "0x5e53Ce97fCd3AbE46F1aB45abB3e9A9C5e19F193":"0.024894724024", + "0x5f7010C06296b593dc9B680Ce745eA2101700A5f":"1.604885777643", + "0x6015B208586DB5BFb398154AAdC411F46b96F485":"0.032589860378", + "0x602b0c3a8A91C61a3b36B3ce5ffcc88D5D7bdaDC":"0.080913825375", + "0x60a363530ec7Db4fE74F5EBe62c337FDCA8eFe0F":"4.388490109516", + "0x6146532Bb9ccA4bE0a087577877e2361f9FbB5c6":"46.783682965286", + "0x617E79672a4c1702878275f3e222ec22621789c6":"0.349293639152", + "0x61D7D4227318C4C3Bcf469366433c37C40594291":"16.652084830812", + "0x64498D361ae2d1f29d153FEc3Ba580F114140858":"135.200817043675", + "0x64f7354Fb18B7CeE35746078E1521B93feE6E061":"11.944546164470", + "0x651C636fC2adbB79fB2c8FBb1Cf3A6F76ff1fDd9":"0.087065874469", + "0x657A38e6994CB578351376dCDb077330D00665d6":"0.155136711315", + "0x6684942f6Bd9554adaD63A6DaDD6aA4996f1b591":"32.797521308325", + "0x672741236e5c5F704503984dC400d505d06BFE5b":"0.238924980731", + "0x67615b2cD47960E5598374A0dDd7ab1CdF559Dd5":"0.014346444477", + "0x67941779E59CEFDBc61Af9Cb047d44C173301795":"462.253975579901", + "0x6889f0983EF3dd687C83e2eE48F4B6ab262A7907":"0.034204818891", + "0x68923625d639937bA7ebfb3a8f6533420138E6a1":"3.472350644635", + "0x6892fefDD8DC620FD645646f7AdDf3Fe72787492":"1.816447965372", + "0x68EBDF71B3A713C71E995D22a2d54Efe0fd71742":"0.679144022534", + "0x696997e2293B519889ea790f14D18aBd104F5283":"13.290199657778", + "0x697F5736eE44454fD1Ab614d9fAB237BD1FDB25C":"0.057889673884", + "0x6A68B9CF65d64D060854f2cbB6825B6026BDe920":"1.583652432869", + "0x6D7C9F86d84F9535D68968B4464197BB1Af2B9e4":"0.450723085923", + "0x6D9A6049752A638beaA1733f5b3e6DaAdA862BB4":"4.385268473813", + "0x6DC735d08458B2c2802163313F69a152CB5C7a1F":"0.008130547878", + "0x6F78aCdf39D0DE3487F6CDe929e9fdFAa541F8e5":"0.451748717627", + "0x6F9Cd1EB49CaeFa4044dDb4ba767B188C69C5a00":"16.291570462173", + "0x6c593854C12898fee80a22f15bd2BED7217E61C4":"0.153799447925", + "0x6cE699E7b3968fd238A99F2dccf104cBa99F9B9F":"0.885104608166", + "0x6f63945408c16531c755eEcEfCDB07C1312a3155":"0.607840635978", + "0x6fb651CDD003A7EBA1998E9c77d9EE0eca102e86":"0.087707377196", + "0x7019Be4E4eB74cA5F61224FeAf687d2b43998516":"4.117116922152", + "0x70C9666B338795DdAb8f4bd67F580B1D9234b8Dc":"0.027118562428", + "0x71BE7958a859b7A4f24D04C7E1a22c6B2026421a":"0.009025834908", + "0x721675cc9129bF75935DE33Fd749f49f7e45b046":"0.057997806397", + "0x726bDC1C6692fEcE418d449F4ffc36B81e0B4852":"22.674606411493", + "0x72c3C4B015f7C6C74e5443eeB08902AE0Fd4258A":"0.045329848974", + "0x73A651063F7E3169624c05d76F728D489a97dCD4":"0.706484342512", + "0x73f432dA307525B6590c858bf458116322D50d16":"0.008782062890", + "0x741Bc936B183F8bcF807C0204BeE62Fa68eA561f":"4.115810901373", + "0x74e3f6338d2ad2da1Be24B422eE7b00C4e46D9cB":"0.061852078761", + "0x75274AA765789B4b55eBE2fb22373865EFab4358":"4.597251865659", + "0x77956E669727f8F081eBe6171e7d2E3ba832e099":"23.546940614032", + "0x77cB71Cf7421d690583c12a302fBB219EE87979D":"32.538737591810", + "0x7852d40F2a8Ea6C219A91aea2249f5a3F70D4dA0":"0.799431088120", + "0x7A6039804CDbC28BC7caE7Ff6341Ec76FDdb254B":"0.001212916825", + "0x7AbFce9bF06C6CAefDeB03b825C6F28cfF55aE90":"7.810838595364", + "0x7B716f389E382a05a2D84D41AC7866b1bceAee33":"1.053190963233", + "0x7Ca34EdAB943bDbe400B5450BA675dBa1d55FC20":"1.577067555617", + "0x7E24ac74A03Eb5bB60bDf2A3C9e142cD7DD36b7E":"15.982079012585", + "0x7a07F1Fc1d0DE73455A0aB0C3dEd66a34f5d985f":"1.979155527423", + "0x7aa7a54D58C8b2F2a8559301C880d3cEd10B7e55":"10.520694384590", + "0x7b0c025529F5E5f158fd1278415Eb156ED01E9D1":"105.799540921279", + "0x7bd82570bc838dEe6915B70059f554152C98A8b5":"3.056418642540", + "0x7bf623AD0f735c87cdD22D2781FE03C52CD082E0":"12.646403062905", + "0x7d3335fE8189F655F466603327d00D6ABBF6C441":"0.003273764169", + "0x7d935eA920F81c7d756F9Fce0A5639DCDb2E9839":"3.132216224696", + "0x7e98d97e0703a79299B65cB57a992a77a5079678":"15.660317919543", + "0x7f137a03bCF3125178b634689b575a746f4A1074":"0.039180604334", + "0x813FFbF5C311cC7750cff0e241dBBE0dc01D3C51":"44.890747353512", + "0x81caBd4705fBe5701acd979DbEE416c6eEf31d4A":"1.590458584927", + "0x826ed9Fe9D60bc408d515BcFBC1E859b2d279b17":"26.015927247797", + "0x83c0ED34e4B8d2a197b79A7eA401b9a37e1A90bC":"10.289805195706", + "0x83d6C448be2f9a6f770C07C673c360a389C70b8a":"0.353652453323", + "0x84a042Cc18d15aF0b5a1bA7CA77C634f5cEf07A0":"9.148933131906", + "0x852E45eD1a6502406Af4c409DF4e0Eef3CC0A0E3":"0.105786296594", + "0x862606a6012fA68019EE26ce4A3b35Bc2684BEb4":"119.319704439777", + "0x864d69e84BCBf88dc63c0333501B1db5D3fDBf28":"5.040368582689", + "0x86526f570cA6c2C9FFb4A535fFe50fA56fD9Dd87":"16.368588533181", + "0x86bDf1314dD3BF4d407dBAd7A2c43261916708d7":"1.616278875620", + "0x8740D9eC65b40bE5EbB84BD22607e81260fe3a3a":"29.434711115248", + "0x876B71aA426FAD5127910D3CC792e818B8229CF1":"4.492562707292", + "0x877DDd15f1fC47E6336028013e15909Ec0C94249":"0.074670761924", + "0x879c138C6dAF7f7637c49F1FB584b9338F9beC11":"0.418188606464", + "0x87E62D5c9379C972f0A9D1411D8D2751CBcba27b":"23.508060213740", + "0x87fC1313880d579039aC48dB8B25428ed5F33C4a":"0.041553907965", + "0x88DdD3103a8025B8C97A9eaBea22005716Cf5e63":"30.364283599881", + "0x8A47eE6008aB47174a90019413746309eF6D13DC":"0.576968133518", + "0x8B2856fBD13D98B30c7bd47114284Dffb7d223c9":"2.336856866701", + "0x8B947a3f891B0eb514372689f40737B76c9eAf80":"5.298334811322", + "0x8CBA226c4a94CDFe08f5711bB870459bc1abe0eb":"32.573255138027", + "0x8CbF96319b3C56d50a7C82EFb6d3c46bD6f889Ba":"0.000947377114", + "0x8Ce3d6bf7578df7B44311ce4a8fd5d3d76FEbfBC":"0.024164439060", + "0x8DE3c3891268502F77DB7E876d727257DEc0F852":"0.007137598758", + "0x8E2eC6F44dBC0C9d831c9C444246aB724a2E6A84":"25.209808778704", + "0x8F3aA7B8F863c26c5669830B2Fe013a9A6d60BBE":"0.658393755152", + "0x8F3eb3Aaa780f48f73D94d749A67B66ca867BaE0":"0.005544666159", + "0x8cb3b23568877583Db17879CC1A5dcC397A328FC":"21.428748074896", + "0x8d6e4d3cD07342BF8006f962Bd8B67C752568334":"8.487343546549", + "0x8e5aFAA162F224381195fBDC24787CAf1B4d24C8":"17.160187182956", + "0x90165030b38807b7764945ce5AE21Ee5B32eff74":"0.095751254822", + "0x90F15E09B8Fb5BC080B968170C638920Db3A3446":"0.487609678146", + "0x90aBCf1598ed3077861bCFb3B11EFcd1D7277223":"407.108617015689", + "0x926Dc3566F2563a8541968Ddd40180C9061FC06D":"0.460149333735", + "0x92cfE98Ea050A12d237eDb9b7901a15AaC88379A":"0.005801330831", + "0x931867863Be460131f305ebce2feEe83B839d5D4":"1.402016629159", + "0x9363080072da9510A469896E438A999a2C1Ca1f7":"7.358793429739", + "0x9397E20E9DAb0A814305Cf2953552C78A1E7bE0D":"0.453594293326", + "0x93b52eBcBa12cFE758f13285147dbc3b43a9896D":"386.682439732793", + "0x93b6F2f5Ac91596eFEC805acCAE0C04c00fe8882":"1.197499773747", + "0x943d616A6f0C973d93E08efa2501f935502B4B03":"7.880451445925", + "0x951b7393A03805573cFE0466682e676eb591834A":"15.855329845251", + "0x95715f56D9fAf221a848CA6dA11BC603C53DC95E":"0.368356533049", + "0x9582608416Ec5F854d2eD7e76600d175B5d23198":"22.585549923473", + "0x95F8D24cF78Dd25ce2FD9C210B18E58a972342a3":"3.421563401829", + "0x963eb0638A2FE075269b4E83F886e89d855ea4A8":"0.523296863124", + "0x97A29acC9E0E746f7c17BC545e8DE506db759ECd":"0.266118363905", + "0x97Ebf22E3eD3ff6573636D4aFEF82Ea0e4dA9127":"85.436322927125", + "0x9814C2c721244Af99dbcc314Bd9F53cE4DD905fE":"0.103763285896", + "0x9833Eee8055c425375DAdeB12109C4b3bA667d79":"0.878945981273", + "0x991EC11010d380002E1CE66D3860aEE12f54629F":"4.530891327468", + "0x9936bffD822149Cf9cf0722547FA720159Dc5fDa":"16.642959238103", + "0x99C0E6927F11845F13CC09c641748A4849203615":"1.088061864405", + "0x99E6dcB5f7058c2c3A8bECBEF2A257e392Fb807a":"0.090867120321", + "0x9C0790Eb0F96B16Ea1806e20B0D0E21A31DC93BC":"0.136243998901", + "0x9C21ad12A38b68259A545baCDc7B3BE42a580976":"3.020897244804", + "0x9D32F1e7C5fF707e92b4B30937A1e5E2C2De2462":"0.298193274420", + "0x9E8c8e6Af3d811ce281b0e6889c57e8b7A772786":"9.431992212156", + "0x9F6C47623d23FC9dBCa3eD670a43792DB3aacaeE":"0.005640604457", + "0x9a141CF0507c1bE4380552FDB95C50C24b259ea3":"27.431097246784", + "0x9a4773EeEE73e34e1EE0E9A64E4b7453b0b04246":"8.396487452436", + "0x9b2ca0B27415ab8ffA9d7FF2065C9bdE53462f46":"0.021432231736", + "0x9c4b88fBE06b6B1E461d9D294ecB7FB0c841dB4f":"1.998705725570", + "0x9cA8A0f39230D3a478B78617201A035c6d23Fbb0":"10.410911491075", + "0x9d653D68DA8d218Cd963bB051F17228Bd51Ea070":"0.631442295805", + "0x9fA10388D2BB8b7d8a2c8586772876D6302D4cB3":"7.433210147527", + "0xA07D83334758Af046DbCEe6991E341C0E061d121":"0.871452153149", + "0xA2ae59055e4B0e73A7e3F1A208062De2AEDee9be":"7.017821036018", + "0xA3a008b6f2BF51D4fc0Ab32Aa6b2a41c3A179833":"0.010496871364", + "0xA3dFB42724734D74d1B66f6F12a4AfA5D7D38DF2":"18.106446072949", + "0xA535d4751d3f6A83df1Fff7C787b9ac396E93a20":"8.839753466611", + "0xA5F3B718A76b22659aC8cb34A301f557926619c8":"0.639804297147", + "0xA7337bd8E6dD5134f3Af68a97C1f73Ca29523C89":"0.074014146002", + "0xABEBdB7d35f38D519d1C3ED72d3975424F420417":"0.388558173926", + "0xABf28EF8fC76326f89765609310CCbFCca291d19":"0.011811364708", + "0xADC031616771eCB503C65e607C13dDe152d459df":"0.311901714099", + "0xAE80890253a09F5196f9e0B843cF32dEF399C92c":"392.411702800167", + "0xAe053731E3b8705d3FB245D24d0300f6993a871B":"0.005799296897", + "0xB1EEF097cFEB4009Cebc3af3DAe3187D6ce57FFF":"111.934564868520", + "0xB2c7FE39cC240453CEA4cE03067d67b5fD049e20":"0.091176694472", + "0xB2d6Cc7098C3AecA089b6580A44b34559cd40971":"3.054709411579", + "0xB34F12a1484999a67935d3D2a85080286B3bd855":"0.004529529754", + "0xB35E71b6A5aebB07dFc3aA705968411ebdBC003f":"0.547934807385", + "0xB4Bd807C9cDde19AE1498c7b7006713268E25997":"1.050327418824", + "0xB5823C3b01b3CaB24206C6Eb0aE1BaE3fF33cA79":"13.836739656346", + "0xB58CEb3969C267ab823435D7b5690CE02c9617e4":"22.653680897226", + "0xB618D796bD7eD78387C7dE0e08BB9868e98fcDCb":"1.012298655627", + "0xB8e34DcB781Bf6FA75b8d48d19c429638a98F284":"1.713982905646", + "0xB9542F9e7AB332346C818483bb8f3292a0653aB7":"0.748599267832", + "0xBF39bF63bDC221e4d2c41FEB5353F223031492c9":"25.565060774746", + "0xBFa0095523d7e2e76b30246a66Ec3481f488599e":"19.316475461246", + "0xBaec9fc6cCe5789fFd5b9831223658e16352b303":"0.809454441797", + "0xBd5f593C86319523e6E8AfE60dEdBEfaeDBdB8C9":"0.077849989819", + "0xBe77a129Eb5eeF922A0867A38EFcb2B6Fa6C63E8":"3.105568570663", + "0xBe9e265c78a22e31d6a41Fc2710D9590ED2d5a96":"3.649757361589", + "0xBf97Af0d72Df8F15D40726A3f6618346395F3E66":"41.915578821011", + "0xC06a89728621DA8D9EFE5e97E905E61CC6AE9377":"0.941843828759", + "0xC06dcd3F22a393ae29Ca3246FC01353742933378":"0.809719232290", + "0xC292EAFd5422A9c961CC2630CbDDf04c93Db33bD":"6.105277631376", + "0xC356E8fFEa6c866bE5F293d5FEe3A39c70e4075F":"39.889747151160", + "0xC480fA7FAF9E22b4756287770E9AF51716b0d335":"1.106120543043", + "0xC5856751806C729d0D9ECc6953c6eD23ADD16D64":"0.091319130006", + "0xC5BCC6C6da4996f1351d2F51c9570B5F60821c8F":"13.215021616781", + "0xC6783Ef814540c0B6565982feBbc0FC5BCBDd1E6":"0.134747640534", + "0xC69aDcA4485C0C494f34e9Aa6b0A31f343f76411":"3.976622978702", + "0xC7719Ed61cc102cc0472E3739055B07857ed2DC2":"0.121422090119", + "0xC7F3A66f240B174cD4687854BC5cB90b592A58d2":"1.182269971713", + "0xC8b2227E1af2DB0f7d0E62975C716a6aD1805Fb8":"0.226473775447", + "0xC9420C9349120CA88D6C349CDF4Af038e2Ca5027":"66.462176231698", + "0xCB2A9c2CE7e3d060700d72Cfe5E6d2BB9959f2f4":"4.510694981827", + "0xCCC3ac360ffaf97fFd1fD274C60DA04DeBEbe099":"0.226017424113", + "0xCCE97ecbA396Ecd63433677fe7D38C01CC5acB8c":"0.040590327043", + "0xCD0e675a32ebcE86c78D1fE9B1E406899Ca7F8FD":"0.365246965235", + "0xCDb0884CdAB00353dBCF8874478d16d99Ae35A28":"7.494205221295", + "0xCcD1B2D70C4d1eFB25C527Ccce8cC8BDfe7c5Ea2":"0.466317148849", + "0xCd5eadBC411Aa119C00a6836cc680294be49F779":"0.711979883376", + "0xCeBba4E9f558716315FA543Ffcee29444484dC30":"27.452146801996", + "0xD004E55a84964d82341eE2ae58476de7D867Bc16":"0.495164214296", + "0xD0d919780117d4F539887f393d3a9188d91A9022":"42.214732519419", + "0xD19a40c9ad255ac0ea9F771f0D6f08D0A84F1554":"4.434347840675", + "0xD1B9dc34dcb85e970DcCAAC6Dd79a3810B9b4bF9":"0.000873277512", + "0xD1ccC963eF8119D935EdEB42e651f0cd81B83B77":"0.375854583223", + "0xD22581871a3f9908568124d9a50EAA1bC122117C":"3.846440870094", + "0xD663eB208f69Dd8e939327472FEddD3E9F58B34b":"688.841246791569", + "0xD9193C548Bf4b1a56E4571b7fF42EE67B3FA2C87":"0.000018105394", + "0xD9801B940A30908f66DF2E86c4f430BCD8178a02":"0.010884711477", + "0xDC2E88dD9bDF3Dc44636C7CCAC86AC6DbAE0a8b3":"7.570374085090", + "0xDaA00b17594bfB23fF2d6c579C57E7B220d2bab1":"0.034015179701", + "0xDb965BBAD97d0784Afc22A5D82a24c5478fAf7F4":"1.260496214859", + "0xDeb9bC9527B27619dB786BF0DA026D60c6F53C95":"0.038511431670", + "0xDf7d7819649a25Ba46C62Dd89EB789a4Ec456aAA":"0.692654512833", + "0xE0b458936fAa9BeeA9a8673372e25B63cD5E7543":"1.991896701302", + "0xE14a13b8eB93B6569a748EA57A1A97025fc82BE9":"0.000016230223", + "0xE23756866b46A6666D3FF9febE3d19c3868f22E1":"27.470167912245", + "0xE45D85B382EFd7833Da1B8CAB53B203D22340b1a":"32.061971064291", + "0xE4da32444226Ab63b1F8E9B7322561b6E9314Fc9":"1.264691528879", + "0xE5A3D77D7788A83f3549dD7F08Fc2aCed7E262d2":"126.235005008491", + "0xE5FaCAEF11b03766cf44A9bf629F551FA2355E54":"60.893544189070", + "0xE60458f765bc61e78940c5A275e9523D1f049690":"105.876100190529", + "0xE6368DE4335F10D55d64BB879B7f3D151F8825Dd":"0.009345433902", + "0xE7294495206c1F5223D7F0C59B004Ce971760541":"0.329677686578", + "0xE76Be9C1e10910d6Bc6b63D8031729747910c2f6":"9.677713152409", + "0xE7BCa65ade947Bd973078D3b8886A123174EA493":"0.121956716154", + "0xE805Ff9b9bf7fbfd9EbE13379fC8E470025da0C7":"12.728627767355", + "0xE85A3ec4CC1efB0069c638e57216b8fC478be6D6":"3.548682637736", + "0xEAC2C3d4f65CcE2Cb9197221934C04a4d1fEb863":"7.377885590647", + "0xEBAfEEeE4ae5275Fd6c064A589B8Bcdef4e554D1":"0.258407261475", + "0xEBf56B07485688Bf467e7C048EfE98922156b811":"12.242611826052", + "0xED3105a0eC578908FF55073A22DDa66943605e3F":"3.163557542038", + "0xEe2319094bBB69c3DF5f1d2E5Ef62fD2F20030a1":"7.381746747270", + "0xEf80c44d996EbAC95aA62589986fb3FCDFE3f7d9":"11.400297546549", + "0xF02a31f977b52801322ec4891B1981CBAE182d3b":"3.067354070827", + "0xF02a5A6d2A16C7322f413059530FFAf2446AbC8f":"122.213974305460", + "0xF3531893C505392bD5E0719729FC53999aB83491":"0.004105379161", + "0xF42bE2Aa2e0B455dF33164Df69d8e8B9c6308cC0":"0.002204047125", + "0xF4de53a234DfA4A16feE8e0e01FE1CDD53b53232":"0.023413947004", + "0xF530E2979EB6590E39AfB51C0C1FBE05aBfbCEc5":"0.023324512255", + "0xF77302872064A27a50df43AD1f397A0082567dd6":"6.462767150591", + "0xF7B18e107eb36797f4cE36dE756630B9C30969ad":"23.015341893482", + "0xF913d7846fdf67CFe638988b239Ea76f2e081103":"0.005329795661", + "0xF99403A51BD63f1101967EA8087acd5f68986096":"6.161488088794", + "0xFBA206E73709F614E5a85afF27A98692d4F3c579":"22.973614317792", + "0xFE4F9b23A2Cfe38D022D27C18376bAb9256E023A":"9.265649964781", + "0xFF16d64179A02D6a56a1183A28f1D6293646E2dd":"17.421676100595", + "0xFdB6611737be90738fce79a001C44Ea2bBDF9a21":"0.000664429421", + "0xFe399dbDF75BCDbA1ff51CA88Bca6F2DCa6d458b":"0.379882324907", + "0xa06aaA0ba0b3Bb7cF89eE2D1C82d3E0bcbD75e19":"7.577785590396", + "0xa18040cf6f97b4fFA8eA3d81F0788FEd88BFC988":"3.062818363374", + "0xa184c0A1eA94B409672469C12d9ee0D1aA13aE37":"5.385421272936", + "0xa2DAA4eED9BD8B2a23EDB163e6C31241368DCC43":"3.553154740612", + "0xa36A09415e8Da9373c7117e5B4e3bC15b803459C":"63.120619048335", + "0xa3FC7fa34aCAeAd9aE7d19622dF5E58850450564":"1.102360660852", + "0xa42c2bAB6AC560db8E59a9C5aA926166C4D4c582":"1.035179638631", + "0xa4507877aEa6fF504aBC99c256192e78F5124274":"24.779871462679", + "0xa4b26aC6092F39B9afb8f5e4ddB503337e5fACd4":"0.027525665159", + "0xa5ed8DA239f400141427800DA33B602A039F2254":"0.003808236830", + "0xa6EB9c5B8Be18A1015EE6BF3698d927b982B1676":"0.068498170864", + "0xa6ab5ca03954E8B2bb54e9006efb8e68824271Fa":"37.200127639337", + "0xa6c4F123b99eaF51E63dD31497f4921aD6761230":"0.003066956662", + "0xa6dfBEbf6F3Ae1C7c46E60A1d353387eDeCe70DF":"49.374365475836", + "0xa7AdC93cAA4c0e1E0461483EB6ec79C9D4e3AD82":"4.842356083364", + "0xa946BeB2201c09B746185f75147C6087243D8396":"0.063344986954", + "0xa96704E2fb3Bf5Ca0e974baA4E1F5D938b2Ec934":"0.093747422158", + "0xa97E5Bff61454486b545538aCD574e664502B7ba":"0.215710542141", + "0xa9eAEF87c01B4C46a691862c7Ba94401394B8B9c":"0.049081885442", + "0xaB3219E1a492e14Ef7a35Da04D31Ed592bfC9bB4":"0.377983731393", + "0xaB43805bD19c5068F5C30bC4E5F187E2bB7258Be":"8.211250569976", + "0xaB96Cb3351582398616868f235958B068bce2881":"0.003330920593", + "0xaD7Bcc6a16eEF9BB537bc5a7bF1D7241a5ac744b":"0.058124443286", + "0xaDc8B15a7bD8d3b4B73708bC839bE2fd2e5B05b1":"5.296693586618", + "0xaE7853A2AB2d63E2D9804A4Dd848A4C7f035eBFf":"0.322580839740", + "0xaa55b756Cc30EebB2728Fe5d43d334625e0A0b4c":"15.212998889971", + "0xae77b94a122Ce1d0cD714FE27ddef8652A37C96d":"0.029587039940", + "0xaea8658914cf1EE552E30c8a71906cba5897b4AD":"112.697080865764", + "0xb04180e0d414E51e7a775944f1A6BA20BF955FB0":"4.152601647638", + "0xb0C6d7085fA8cCC6c174841495eb700130eA2e97":"0.375263287107", + "0xb0eF8441B79048C37CF3BF9645762673518a74E0":"1.521165075090", + "0xb181AF0c0Eaf08Bc3160Aeb653c867eeE87e68dd":"0.007246707017", + "0xb4633912c7374cd94D8917a167449e23b46d0AD1":"0.005984334455", + "0xb4aEED235917ac89CA283e9CAA51E8A1325cb62a":"35.781828164331", + "0xb4e7419A42EA01c418998FCc1df2ac0788e97566":"7.506073357099", + "0xb672246188E98faE363814E6d2566E3058F7ca46":"3.058478698130", + "0xbAC8075dB42624F0FC9d81E02942fF7c85f9A7D5":"0.463838283477", + "0xbB45a76388f7d9148A39651290aECA5dB3738798":"0.799922727555", + "0xbCC5C7180155a055bF87d97531c4584D51074c8e":"10.538678111718", + "0xbb3E24b2d16dcbb0DF7F663FDc8a83A914bBC017":"1.645666010860", + "0xbdF81b19af7848F7384c38E68208885ff0C9F390":"206.527077251129", + "0xc1919Db0c4D778693188c986462d351B2693Aa9a":"0.085166493494", + "0xc25fef376784E9BcaD3E1472575c1E10079c56d1":"12.521880905456", + "0xc4C57c2d1D6bBe6575F7839861252D3CBB79E96c":"26.134775723729", + "0xc674fFaD8082Aa238F15cd5a91aB1fd68aFEcEaE":"0.430442357431", + "0xc7B009237cB09690A4C887e9d4dc233f6F1e9e83":"7.551301527459", + "0xc865B4ECDbAB1d7a369d68b7627e1822468F680A":"5.959717321641", + "0xc875F257fae93BB9Eb664B8b51057299d1527BB4":"31.841712939644", + "0xc92B444E0BC428459FdA180d71058164fd5e0E10":"0.007218800331", + "0xc938421f4F283ab7886f172413B3ABa23988ae41":"1.056414937013", + "0xcAFA7a1Ac382B4127AC85E270dAA6DfA3175EB1a":"0.046842826426", + "0xcC202930867769A83B61cf5053b65D1845E76Aea":"439.711157618804", + "0xcb47A044793227A943709B4F24BFcF6e01f1134C":"0.035153268404", + "0xcb59b8a261bBB6FfCb376b4c198BAA0bb896e6D6":"364.828610816972", + "0xcdFbFA3309fB7e8Bc604F27f42D906f8E62B7797":"0.091923799985", + "0xcdeE3aaDeb1dAA89a415eB164737315026782af0":"0.012230674527", + "0xd1207f05eBE558D32057b3e569A436DF1F46Fa05":"1.959662604993", + "0xd14e9D40278B9ce3daA887414CD5772e847E2bfa":"1.314757594943", + "0xd16b73d9bE64A01af0200d4C927B4A78b00C7F59":"0.119750979011", + "0xd185a341E2D30df901D4792F52af0265b45D7D9c":"37.342442592545", + "0xd300c4D11702F5E726590857250bcab5b927C7B7":"1.655630171594", + "0xd334533980C201c1b93e39bBE6bD1E04be5C0f5D":"3.181943573688", + "0xd3c427D2b51854b8B21fEFEC0ca362454C23d365":"1.786890116716", + "0xd861415F6703ab50Ce101C7E6f6A80ada1FC2B1c":"0.617101926587", + "0xd9020D30AC868463c8B079430A2938263C43Fe90":"0.783345926199", + "0xd96A1769FdcB59DF2f5A18f91dDF6cCCd7002516":"0.047268943812", + "0xd987de72bFA8bd1fc76F7c7051EefBD72031BC1D":"0.043510373691", + "0xdCD88f18ba1c11AE9CE86763cbf6FEB475544790":"0.467943618842", + "0xdEc79CEE4397202739ED948ef98846774FEb53f9":"7.648353457199", + "0xdF33800A566f993ea50c409a5D85B7170b60C0FD":"0.083847333691", + "0xde7e8fB53E3Eba9D49cB0bFbB9A93AC63Aed07be":"5.398990664927", + "0xe07E487d5A5E1098BBb4d259DaC5EF83Ae273f4e":"39.282765805946", + "0xe76D4165967fB1ed8D5a56d5DEa87e3bc8ECd3AC":"0.507825401658", + "0xe773b3DC818EBBcf13F224da7F17c86f87aFD0dA":"0.461857745195", + "0xe7afdc19099d9FAB3e76b4297F5E7D3B597a7CFB":"0.380159350152", + "0xe8ec1272503a63b57A02dc808E4d41D2BBD22E9A":"6.014098974452", + "0xe919047aA85E80cC19e619a081Dc4dd5620803af":"0.290599234709", + "0xeA74bF7E0Bb562E2d97d11c5A2D94B0a900d03bD":"2.262174468844", + "0xeAa630449b584b7517318D046d78a4538c7E1FbA":"3.529925841932", + "0xeB312F4921aEbbE99faCaCFE92f22b942Cbd7599":"1.908622821436", + "0xeCE1477D3C350a42486D2DC802F6243E99409A41":"0.254935597569", + "0xebCFD08a0a9a389789CcB19Bd1ebBeAf91f4c134":"0.980068844423", + "0xeeb7c600722630Dd0b7698a23236E8465c1d2D79":"0.751615139790", + "0xf1EFb5235308B6736c31532002Fd36fF4ea91782":"4.511231797247", + "0xf20878614c5F352B57Ad7B16785140e73e69D602":"0.020676111272", + "0xf32939c033830042a5F2A25eb051670267a08aaB":"6.842919853472", + "0xf453193221c1465c2E58315E01689b7088819256":"0.967649607803", + "0xf499884e8533F131Cb98bAA8aAC14252AeAf1d44":"4.439071273747", + "0xf87809F81E30d59F7824066C19F74876f9254657":"1.130311350929", + "0xfDd3631ad0eE38c3F49A1eaAcd2324e430e29Bb6":"1.397403387465", + "0xfE36dee625234EAEdbaDE27C15405eB482b81f1D":"0.325233924469", + "0xfa4a45D755eA1c2b72Dd581b3E05dde3bFc13fad":"1.110334655062", + "0xfb0A954fe9378b2418f59a94A4D41A3953F92cd8":"1.052141902170", + "0xfc25529b34482646527Abc46896c71dba15BcEAB":"0.314764664894", + "0xfe5660f2e5bA3012c4e517831d23EB0F97188bb5":"33.499468610245" +} \ No newline at end of file diff --git a/tasks/balancer-mta-rewards/20210810.json b/tasks/balancer-mta-rewards/20210810.json new file mode 100644 index 00000000..081b7e99 --- /dev/null +++ b/tasks/balancer-mta-rewards/20210810.json @@ -0,0 +1,637 @@ +{ + "0x00010a708585bA4812A1c5976182626C75cb7A6B":"0.022413875068", + "0x00329E92ED6fD0A0ef76DA9C476D51FaEB6B26a0":"365.142162884514", + "0x01222A114552e89E4C548170f8c1f4d5460D255A":"17.659653890304", + "0x02c4A98CcDfcA694807554BEc1cFf46152b6d3d8":"0.021675284241", + "0x031d132aDC8141130bD55bE6583eDDed884497F4":"1.347080460487", + "0x0351B0B81fDD1DB5dAd0617D97c598CD3C37BAAF":"3.233483467234", + "0x03886228BB749eEBA43426D2D6B70eba472F4876":"15.862340701863", + "0x039630fa4ad7F73738B1541daDF3BAD89fFeD668":"0.205214585979", + "0x03f938F62fbE032d3197b4F74fA4c228dE73088C":"1.386426878682", + "0x045C7Ea70B3Faec4B39b6f0C91e12cfd026ACbfd":"16.050767744064", + "0x04F1d25968CC1aD76e773d1B06aCeE6C502c93dD":"1.918157552564", + "0x0553aeaaA787cA5BBCA6E172D3fDE33957ff4B91":"0.179930962272", + "0x05928B30CdadF923980ad00655f89349D08751fD":"15.871727320237", + "0x05B258373d15956a927C3a57C3005e9D37fC0949":"21.584475090092", + "0x0628bE35ADF5B97C90d15fdCB7bE30079b530Ff9":"0.000573698285", + "0x06Ecc3d947da5A37B512F25604a56a0F2F22CE30":"1.979353050047", + "0x06F2E2976348A38e9e45B224C45AFB19764135Df":"1.195894605382", + "0x0714954310cF39878A43dE5f7f0cE65D5C904e23":"0.089893115673", + "0x074396D67D04c48fdbEf50af95df216185B63cD5":"7.201149211126", + "0x074a25e8A66Ec93a6d68eb6D92Fa1bD665b82301":"0.242490997953", + "0x07619FBeC9132d12F81B8de9E6a82E6de2589765":"121.691158895763", + "0x07eFAFC95A0fB31a6216300182350712cD1aB333":"5.179242056484", + "0x0855f651Ed69B60A3104C7fd7e3FE6F8682Cf5D4":"0.207533280240", + "0x090Aec8Be54F0ED6105532f0404998F55bCbc72D":"0.477479018434", + "0x0A8e20Ee171630EF9dfeBF02149169f90c133cd8":"68.189532284246", + "0x0AC2aF90Eb5Ab7ec9901e81500f3045D4273e68E":"142.165209955978", + "0x0CB3649bd6d98fbfc3c9A9A3dEC61bB300a3A5a3":"0.186246908369", + "0x0D5Be16B4a2F17DAE8CE505D4fc37189798899A1":"8.981011729712", + "0x0E2353516EB6208aA84a552b9a1EE5f13eDaDa57":"8.533502581251", + "0x0F2Fffcf71707b1C446DfD46B3643a19864d2D0b":"0.424988613890", + "0x0Fba26D58C3fC3E0e6cbd6A4B5394C702630ecCC":"0.322941098867", + "0x0a366592E4D38e10C320658CCadB4228197a51Cb":"3.505844190955", + "0x0b3437987D2ED94C58BFE42abD3Abe1f4d967864":"3.450678183785", + "0x0bF6a86F306d7a23d206C8C9880371C829408F9d":"0.165006961074", + "0x0be5149120375A4B5ba59352D55173C9B0283C3d":"0.404478673838", + "0x0cb58FdEa97c1471ac5f72dbBEec027D4836b2dB":"2.475813419725", + "0x0e22b17cBc92CC9BFAf06E875dFaF1fDCA5D3715":"0.004438000162", + "0x0e41c9B548cf0545377AEEb693A6006172F1af81":"1.675857344811", + "0x0e5241B3855A6d0B010894d59F505F4225444d7d":"0.113616909434", + "0x0e54EDd4cF6568DE41fc67eaa71AEB52885999C7":"0.018909147562", + "0x0e61dae710688C22d8f6D0C3Fdd1735d27dDff8f":"27.041987680645", + "0x0ee0Ae881c5F70A6aC29858508fb62b54aBBa807":"0.085154819053", + "0x0ff68c6aFe35cC0B5a472A3AC31DFe0ff05AE4eb":"0.126383380611", + "0x1002F408759C0bee0D4deeD7fbF1cC1071210f4D":"0.003580970334", + "0x10061123876CEeA12642342AfE829444b7E0d48a":"0.392528048971", + "0x1036BEc790426A3dCbA370a66C3a0c5E15D7f979":"0.503937220558", + "0x108156f882a065148e1De97F877195d6C4942B94":"0.012495089284", + "0x1107602d51193953cFe4cb8a4B01d846e7E426ab":"0.084290987413", + "0x119460eCc4C538a02307e5D4f687BB83eDdC5cf9":"0.017718905493", + "0x125aD150043F25A8C0A09C8b82Ac2a26EaAC1400":"2.776028859041", + "0x12E007d8031004aD8AA582b8cf5ec0Daa7EEE9F1":"0.778188346220", + "0x12af0CBB90edEdCf871b07d3d9F41EC60E24D8d0":"0.695984586804", + "0x133CBA6d89C4151f5B622a37Ef7F60F9FD9B5760":"14.571050069338", + "0x136200E3728fFdc1688e9dF19F0B6878422c0Fbf":"0.013818564362", + "0x136499A6afB3D429998674D54CF6965f710b88CC":"2.429548083611", + "0x13B6e59a6209031f205ef0472973f45a059Fc62E":"8.232590961946", + "0x140a784674E57E83b6E897693EbB14A74133887E":"0.353705859063", + "0x140e7364eE4E29a677a9d6cCF26ac945e7E80B65":"161.873423477953", + "0x147FDCf537556D459fFb160a61297C8168507e81":"13.115267368095", + "0x1495dD855f2F4C3A62492742cffE74f8BFbD42fc":"1.764624215033", + "0x150B43888b42d00E72f6Fc1278286cA6293c0325":"1.322757275260", + "0x156aC26D2d0c9384da427f5f6A69261f49fA8fF8":"0.001099302768", + "0x1755840E9401E8e94A5Ae04B8fCDe3D63700ff47":"0.884813151959", + "0x186b12Ceaf08FDE22E32647fcc800e45876F5Bd0":"0.075392199142", + "0x188Eb6A325EA019C0c192542C77e0E19c4825f17":"9.997373665517", + "0x1894cfc88E1E8011b57BF2b6285Ed910795a2b5A":"0.077595786906", + "0x18e692e3cb8354B9539C4a1aF15bAd67A72e004C":"0.173082214220", + "0x18eF587571de3bCE0278f0bAAb5ef714D69687D1":"0.048003948535", + "0x18f768455E7f5fB09fC491fd86bcc282BcDD5973":"42.978584767681", + "0x1944CA723f77771B2b4EC10110494eff1fB5Cb36":"0.512171712776", + "0x19b7F5fc443Fd4d74afBf1886b17b4DD47dC8EE9":"1.768577928381", + "0x1AaCEC3803C5Ca4747622049bA151d1bC3F64543":"0.717943617413", + "0x1Bd8430199409206ee70e29932b04B41014104d9":"50.752078266275", + "0x1Ce99932fD278E00911814dC4bd403e1293d8ED2":"0.004507152831", + "0x1D9880e072a3125f552960F2e433df1981A5d237":"5.593032258118", + "0x1E313878498b9cD2c04f6594A2a288374953E93C":"17.995756025547", + "0x1EF454D9adA201CB841AdEc2f48944Ad918cD179":"0.240856425566", + "0x1F15B9414451B57e129f3D22C83B33Faa7fe35cC":"0.358625662702", + "0x1F452f2beeA8C1105cd5363b18A0995367a87D97":"0.007749876903", + "0x1a0A64b458B4b70d5C360b164f7bc78ec4d80739":"0.020952260233", + "0x1a39b950c5127D81BB09D884b2cc50fe35eAf6f5":"3.463694092683", + "0x1abEd56b27691Ba3b5c0c68D2B0Be0f1638e3D4f":"951.394904270112", + "0x1bf4D764FeBb13FE809DfA82F0d7ecD554FDC55f":"0.449775295701", + "0x1dbbf91668C2f66169089f778bBDEB5697Fee004":"0.252388570752", + "0x1dc1B489D1dd16402337fb1FE3c76b4c37Fa651b":"0.579114299876", + "0x1e470C6B3e874477Eb4eaDdF7D0F2c590794d920":"62.298430373207", + "0x1eb11A06e78E074DfAf8E38BC9620232ddbCff23":"0.191093474408", + "0x1fAc6EEe2197c98D1ef0F166ECcFFA8632e9a279":"0.229975440908", + "0x209Da1FC73610881A80F1591668a34CC2B9da800":"0.349210168614", + "0x20c343F580Ef3dCb77d40330A190c6F15a74d1f0":"0.000452544390", + "0x210aBF3Bb60261bc5870c00794Dee33CfF00374f":"2.411464765674", + "0x2115E324da67FBee40c6A35563175a8CE6dD0554":"0.501988276320", + "0x21777Dd9320C3B5D120C85C45d35968D771652C8":"4.424499131749", + "0x21d79c2Be4ab3de2802a60Da66f86D497D06102B":"0.560030906835", + "0x2441F46002F1f733cE4BDC62DCF9d5deA357414c":"3.192815469690", + "0x24437F22bC7Ee46Dbd9fedf73ed5934F71d118b5":"0.340825808087", + "0x2486C40f5cBac257217129d681d5A4412BE7328d":"9.200025201942", + "0x24a30823bd87E785B0c4B3803a2bffD91eb6876F":"3.659497780037", + "0x25150ae86241D8128eDDa5039C0ffF7762D62f90":"45.356514782172", + "0x25500AF32Db05eE5Def9175d8e29AED5ea5Fc6e4":"0.311284787723", + "0x25B70c8050B7e327Ce62CfD80A0C60cCcf057Fa6":"3.995225001349", + "0x26602a5f7eFF57FA32D9cfB08cAc447AaA93eD38":"0.444100509624", + "0x26bBec292e5080ecFD36F38FF1619FF35826b113":"40.733992253653", + "0x2701B5d0e417155E7a2B6D6DDfE7f016Ed94846E":"3.674236777108", + "0x27633D72f0611C2F84f6CA9467fA0f50B4eee59E":"7.332857121506", + "0x27CeF68437CB19a87ecE341985691EC8e1EE0937":"0.145195209047", + "0x2814c361BF1289cEf2958dB2fd9dE154f37a8963":"0.314054402686", + "0x288a2395f027F65684D836754bA43Afa20CA09e6":"3.038201136365", + "0x2923Be587904F3034FFAB36C86c495Df3C21Bc97":"24.241430544138", + "0x29825fB8a960c37C52e5464C8053494Fe76Ce63d":"0.000476793063", + "0x29F4BC513421e123fd9A8cd4C159E40405eA02FE":"0.054077369659", + "0x2AA54f9C1fCF2b461347823d0b912E8548846FbB":"0.681851314681", + "0x2CB544EC53BFC40d97eCB9579BEFF2B43778FCc6":"3.751826213035", + "0x2D2170D22d67D3eb3096FeB73b8100ba01d76E6a":"0.000717096871", + "0x2D782B4e65db9F5545774801494b48047897d060":"0.001682757093", + "0x2a9b5fCa68b190b36c8DceB34DD6d97F74Fb3cb1":"7.049920719850", + "0x2b72051F378Bff38c63C286f4B5031a238C132cb":"0.341297710251", + "0x2dB50A4FbBE001608DCBdf8300cE5009A9A47F4e":"1835.140155428823", + "0x2f2AE44e7eD861A4569b50eb57642E5e75d49EB3":"0.751269164394", + "0x301605C95acbED7A1fD9C2c0DeEe964e2AFBd0C3":"1.759238593147", + "0x313638C16B16183BE9AE577A0fab9Fc5b5f60f69":"0.076517130373", + "0x3210418872ca3C52798eFE4844A4F17A95662D9b":"129.569070059885", + "0x32D338aa13C5ea751f701D7864dbDDf862A97A8D":"85.179585193397", + "0x33C2613e65cbFDAA97cC92BbE2F64E83975192A6":"0.576002283512", + "0x34319b1DeB14fb1618818F5ba54F2864Ba09D512":"0.752482906795", + "0x34cadF09dbf50a39D1a68b87D7763DB3961ef483":"46.595175346305", + "0x34cd8a21E92b0Abd558Ff02D6cc7a9e12DAf0ff1":"0.683453005853", + "0x352F1b61ECc2A3ad9c1584BcB7cb2794e55A4a75":"15.459402645904", + "0x358255907FADEfb6c0E4409F3fEC79764990E51e":"0.308087021161", + "0x35cD0A3d61Fb9de11CE5547Dcb22fb9a0De613D8":"3.631291113645", + "0x36fDb65D2d484b036AdE6A2a418B05Da0c848f1B":"0.681172241908", + "0x3724583Ad51c8f7c4aB168dDcD185681Db07Baa5":"4.492008653842", + "0x37450907Ea70a6e9B60f6b27fFCe6056D4661628":"1.643760888003", + "0x374FCF2358549EeE84b564ddfF9497e2887Eb0Ef":"71.447983449401", + "0x3767A7eaf50d992D3Bdd0b33a8DCFEe25c017f07":"0.172714708551", + "0x37DD83EbDE2d144bF68be9a6686A3c9BDa6Ff73d":"0.253553310645", + "0x3866b657403dEA64949c0e0d258fb887a88D6c5c":"2.515613327941", + "0x3A645aBbc55A6a77E7c424D7e64C289b00b6Fa30":"0.238292024053", + "0x3A8C3F087B7c908Cc4d4A0345ECD8C49d7f6CC5b":"0.208112718880", + "0x3AdB88931FB41292D4eb49fC9fbc400e8ac20FAC":"4.217218430517", + "0x3B7858Af7f26CA49C670e57c3B68B2Cdb634d8B2":"1.232626215564", + "0x3D6b617d2C441Da58d4c215AFCee6504274ACd18":"91.694661987553", + "0x3DF1b295d394B6c77415e9659Cc1a7DBCcA76Be7":"5.160856517661", + "0x3Dd6B96Ddd37c086c9Da7966F9b37789034F7789":"0.043366441398", + "0x3E00ff6Eb846F1BAD74fc177186eBC7e45883ec2":"0.654419534634", + "0x3EF60aE2acE351dC6058B5B21Fb566089D118137":"0.016428580209", + "0x3F3B9B0F14DEF5817a61437CCB25CcBBD4853fFD":"866.463208680233", + "0x3Fc3E6514fD4925f55fB3Ae17bbfbca2eb126608":"0.542992575355", + "0x3a14Ef4E56cf355E0D6a08a478fD43b10704535e":"1.745449254314", + "0x3a9c7Db0cb65e9318Aa082d5EA83E327AB9fCD76":"148.862541187927", + "0x3bb012Ed4A33dD7e5D044F1538C18dB172aA5875":"0.242203187917", + "0x3cE6A3bC62D6F1427340128277ac43Fd617C2CaB":"0.018634915874", + "0x3cd0D6d95B95D941FE0acDe2fE6C52E780D076dB":"0.002998043733", + "0x3fcAc584d0B71A9564602C1f0288eC1c0d9846cf":"2115.665434109402", + "0x40eb586F9bD55B1a05b6F840e0b43d4877494050":"77.030002378564", + "0x41546ACDE94953FAdE02CF27a25303a4159D5187":"4.123993955482", + "0x42e74D3683a89f4aFC1E26204b068b2bF1Da8607":"0.053851546197", + "0x439D6dF49a7ACA63c014bCdBD047ed601C943447":"3.332499698831", + "0x43B5217A1f0f5E2dcF8019Ff740f9252a4351701":"0.001102381771", + "0x43Ba3B417E914bED27DB8e16f4e9De0247Ba6597":"2.525366404735", + "0x4644A9Afe25B01405B9099c32FBf123F919d4838":"7.525078593380", + "0x467F5Ecf005293b92f59fDe6DD9aD16132471825":"0.651637340169", + "0x4783281A06E91E5D0A136be35b86BD93E8d43904":"68.127931622548", + "0x47945E744d4D1c4770E0e766c9c14619976ECB66":"106.662777293092", + "0x48A7BEA12A800957113662d27468fD1C9E8D42Aa":"179.675831302617", + "0x48c06dFB7a2245c288494f08FCC5b1D5A0312c62":"1.346683175767", + "0x48ff53A8C1af4Fa6bC4Afc4621AaA016C7C35933":"0.289979265279", + "0x491180FaC09C5A3f76e2Abc2dffaae4500b73Aa5":"1.085494213718", + "0x497f24DA9a889C1dC9b8acbA00d5F2275B5dd166":"35.741239543462", + "0x4Bf1fBC233Cd6109c35a4cd9D34540173Af3234c":"4.649045945346", + "0x4C4baa9907F23c1C51ffF261B518e704Ce1d491d":"1.175890421046", + "0x4CA2d2E5b162335CCB893B0b6CBBA10565B54677":"2.900826930256", + "0x4Cc043Bb273a480aCaEe12BfCEBBB812dbecaEd5":"0.020428291690", + "0x4DB5e2f30EDAC6D35079D6c5E102F2A1bBd09Cd1":"0.094183016186", + "0x4E1eF49A194fd9527c5C8758c5795Eb28B147929":"2.098679222074", + "0x4FfD0A59a26cB2Aa76D403215e4cC2845C053994":"0.137799926883", + "0x4aE525227c761D9DfEfc4D7C268884151a118Db9":"0.000030865812", + "0x4b3f301699a0B12fD47306becB2db8f679bE674c":"0.619624572214", + "0x4b887546C27b2dF2E152fd2d2dD93030706D2363":"23.397670683978", + "0x4c60651f30Db21aF8fD3c6a4c6033C6813C61501":"1.871664078571", + "0x4e954215a990fe55b7FeE2CA508E19c1DA0c8eaE":"34.939714566464", + "0x4f668e8d4e7FaC8eD9151263964453482a30d33a":"0.000312714426", + "0x4f90bE1f8967EbAFf0d543822C2F5Cd59973C9B9":"15.807907936947", + "0x510d0E4DeF20c6BFd84f080BC424Bac5C66941f4":"24.157550379218", + "0x511aC1e6868d992c626ea3474B082e4264902FDA":"0.551871971515", + "0x515317dDdadefC6274e88b8eAceC2BfC7e7FfdCd":"1.494028184575", + "0x51c5B43085E47358047882c74E1a8db17a5e10bC":"0.000733406402", + "0x526aD25f4cF234e429967c296698B963af813D31":"5.662125486266", + "0x52d604536344d86aCb67fac69b5f057874fAA3A2":"1.985337454833", + "0x530115e78F7BC2fE235666651f9113DB9cecE5A2":"0.071283807385", + "0x5337E7d65bbEF09Dc425441cf0929C1648b353b8":"2.991042744011", + "0x537415d516F6e5e748C99b83AC552429481ADf0C":"0.118582843747", + "0x53993eE27f55a92f5A2616e6Ca3e125a9d437eEb":"10.862730951137", + "0x53E7bB9a05717F8cecE9399BBF9C908cd61BB64C":"0.451348118452", + "0x53d338f84A1aeB8a9cA997Bb8083Cb9CD8fb3CA1":"0.047299905370", + "0x5461C09d7b61E4beFFCd782DC333f102ed7F6e24":"0.000049005581", + "0x560390d76af48208DC50A8906A91BaF7Cf93B116":"0.011656669492", + "0x56346a61158532177a33188FEC1954d495C4d330":"0.147066502332", + "0x564fE76BA3544c70B91ab03609A7757648d562f5":"0.361279902875", + "0x567e21f97a3FcD97Fb53248E9658A7361A2830a5":"0.420170219875", + "0x56F757b0202935B98a9c649D478170Be6F7D62c0":"0.384484712036", + "0x56deb12Ef1701cF7af2e0EC9A00D13f8F08299A1":"0.000078423354", + "0x571a7d2B7cc2FCc8360a5104c6eF47Ef1796491d":"0.086822130148", + "0x574378e51d3973ce57Ce0F9a48C29FC06f4f003F":"22.684978210468", + "0x575f436EB9ba16F4181DdFb21d13cDA0E8678EaD":"1.340768042212", + "0x5794201b0B8a166c9F47a12ebd4624932455a645":"0.027072929701", + "0x5890b30bF729109318C72ccba55Ec2755Bf94702":"0.421148490998", + "0x58d6288EeaE05ECE61a2965F8003382a89395C78":"0.485181084144", + "0x5999865518C9de250b820762130FF7A7119A4558":"0.809133517273", + "0x599ED10fDeA23E5c968ea1dE7832011dbC7c9b22":"0.262516951345", + "0x59D5D00d29e8B70cE0c7770DD98c1d6e8E4D2c61":"2.999369218863", + "0x5A0D6d0DE7c74899F09d3509A429bEb7D3b4b1d0":"8.611745284457", + "0x5B9a927481f5870227CEFa85CCC5f78D8719f75f":"0.083835669267", + "0x5C89C420A9E82Ea9AEDBaAab03302e39982919b9":"21.649694251104", + "0x5Eb5a01b3B18dEdC76f574A08368aF874cB53aF0":"11.539764353156", + "0x5F2c54a59986BBc0736442BCBFA6C34EA5a25b3d":"0.001127413855", + "0x5F937a82389CA4C25bb0Ab36d5e1616aaC5d8C38":"6.584339970334", + "0x5Fa9B470E4F38f612b89dE9EFCC652B56d514833":"5.668650297299", + "0x5b7a3a14D0488eaC9c1A1f943A80ECD983711797":"0.149208546695", + "0x5b954bee2845814cf38bD90F7d3F5f6E5f2C7078":"0.024201736463", + "0x5c10afD2E8C8aE7E282AEA60759209f32EdFe3d8":"0.801747961885", + "0x5e53Ce97fCd3AbE46F1aB45abB3e9A9C5e19F193":"0.218002631093", + "0x5f7010C06296b593dc9B680Ce745eA2101700A5f":"1.488754490826", + "0x6015B208586DB5BFb398154AAdC411F46b96F485":"0.172287192341", + "0x602b0c3a8A91C61a3b36B3ce5ffcc88D5D7bdaDC":"0.075058812642", + "0x60a363530ec7Db4fE74F5EBe62c337FDCA8eFe0F":"6.813359577499", + "0x6146532Bb9ccA4bE0a087577877e2361f9FbB5c6":"44.399958276752", + "0x617E79672a4c1702878275f3e222ec22621789c6":"0.324018370123", + "0x61849beEE0Ab2c3AB766145F5A8c102627928E94":"27.580985385982", + "0x61D7D4227318C4C3Bcf469366433c37C40594291":"37.780989682476", + "0x61dE3a78797802911F9A731BA74074a874B0B81a":"0.074732278255", + "0x6206Dd1c5C654D11356Db7a2942CCB2E049Dba72":"0.006031627387", + "0x64498D361ae2d1f29d153FEc3Ba580F114140858":"134.031375161489", + "0x6494D62Dd1b41652696c53724bd5EFe5898af263":"0.136130884186", + "0x64f7354Fb18B7CeE35746078E1521B93feE6E061":"10.657290669461", + "0x64f8e38e19c1B6992969Dcd5769c149951018Af1":"0.179465298050", + "0x651C636fC2adbB79fB2c8FBb1Cf3A6F76ff1fDd9":"0.080765692748", + "0x662e82b72aD3700ee58dD463958e97B57d9f4236":"3.219852737408", + "0x6684942f6Bd9554adaD63A6DaDD6aA4996f1b591":"31.049596449680", + "0x66b0Ac5331551083ce49D224e9045b2F6De8bD4e":"0.002189003090", + "0x672741236e5c5F704503984dC400d505d06BFE5b":"0.515250930190", + "0x67941779E59CEFDBc61Af9Cb047d44C173301795":"428.804773295050", + "0x6889f0983EF3dd687C83e2eE48F4B6ab262A7907":"0.095153198772", + "0x68923625d639937bA7ebfb3a8f6533420138E6a1":"6.098646461541", + "0x6892fefDD8DC620FD645646f7AdDf3Fe72787492":"1.685007807703", + "0x68EBDF71B3A713C71E995D22a2d54Efe0fd71742":"4.322008349155", + "0x696997e2293B519889ea790f14D18aBd104F5283":"26.653429169654", + "0x696c43B3b9cCaF0066288E6FAf8c7bAdD3D349C3":"0.215857294918", + "0x69a255CBB2cc88a7fDF7DA8a44deF7FA785bbF26":"0.006693140005", + "0x6A68B9CF65d64D060854f2cbB6825B6026BDe920":"2.012533245336", + "0x6D7C9F86d84F9535D68968B4464197BB1Af2B9e4":"1.112883213018", + "0x6D9A6049752A638beaA1733f5b3e6DaAdA862BB4":"10.013038308965", + "0x6DC735d08458B2c2802163313F69a152CB5C7a1F":"0.025594965926", + "0x6E2FF642d60d1c99811F0a1A39e1b0250C488Cce":"0.002080872684", + "0x6E9439a97bde6F23655e31BEE01D430313C684A9":"1.068039571373", + "0x6F78aCdf39D0DE3487F6CDe929e9fdFAa541F8e5":"0.076648599119", + "0x6F9Cd1EB49CaeFa4044dDb4ba767B188C69C5a00":"23.308572923175", + "0x6bbc632E1c03a2Ea277be59de74b60F4eF416C63":"11.445389680251", + "0x6c2693F5a936f37eD03CfA8465bF2D8BEFf19A0f":"0.757601941849", + "0x6c57A6397879df7ce5AF3d66e9954f3230127daa":"0.268474910056", + "0x6c593854C12898fee80a22f15bd2BED7217E61C4":"0.294186382295", + "0x6c842c22969dDe2daCfA957DB5beab91c44Cd007":"0.035984077251", + "0x6cE699E7b3968fd238A99F2dccf104cBa99F9B9F":"1.803688479299", + "0x6f63945408c16531c755eEcEfCDB07C1312a3155":"0.563856624019", + "0x6fb651CDD003A7EBA1998E9c77d9EE0eca102e86":"1.159571286110", + "0x70C9666B338795DdAb8f4bd67F580B1D9234b8Dc":"0.025156233648", + "0x71BE7958a859b7A4f24D04C7E1a22c6B2026421a":"0.008372715641", + "0x721675cc9129bF75935DE33Fd749f49f7e45b046":"0.010536475601", + "0x726bDC1C6692fEcE418d449F4ffc36B81e0B4852":"21.129061730358", + "0x72c3C4B015f7C6C74e5443eeB08902AE0Fd4258A":"0.042049731619", + "0x73A651063F7E3169624c05d76F728D489a97dCD4":"0.655362364266", + "0x73f432dA307525B6590c858bf458116322D50d16":"0.050866710216", + "0x741Bc936B183F8bcF807C0204BeE62Fa68eA561f":"0.085939098879", + "0x74c3Bd0F1B08a9D6E444046aa1d81595505f3B91":"8.817937051623", + "0x74e3f6338d2ad2da1Be24B422eE7b00C4e46D9cB":"0.057376394822", + "0x75274AA765789B4b55eBE2fb22373865EFab4358":"6.597597293469", + "0x75547dbdE1370eE1Ff3128AdB5F28Fca196123De":"0.191459211077", + "0x7589B4ae320E71f7c66F3dD6D9Cd4B14F7B7a272":"0.174401072556", + "0x7689F17560b5eE53799f0b37C975927E1258fbB5":"4.552513076683", + "0x76BBBAEFBA0b9ABa325C089f9Af072479B97B848":"1.507772630168", + "0x76c5480ED39E1494755aA49945b202CAA553D7b9":"44.207744650684", + "0x77956E669727f8F081eBe6171e7d2E3ba832e099":"21.843058286573", + "0x77cB71Cf7421d690583c12a302fBB219EE87979D":"30.184199019293", + "0x77d192C06943867e8002ABCF6c9e6A3fe681E320":"0.123153402866", + "0x7852d40F2a8Ea6C219A91aea2249f5a3F70D4dA0":"0.741583381898", + "0x78C9752747ceEb381c147880F4cd01E2E8a1D48B":"0.175413856628", + "0x7A09696e30192974d732CeB3E82E1306385886fF":"0.191162318312", + "0x7A6039804CDbC28BC7caE7Ff6341Ec76FDdb254B":"0.006595240551", + "0x7AbFce9bF06C6CAefDeB03b825C6F28cfF55aE90":"7.245637788031", + "0x7B716f389E382a05a2D84D41AC7866b1bceAee33":"0.976980915435", + "0x7Ca34EdAB943bDbe400B5450BA675dBa1d55FC20":"1.462949225713", + "0x7E24ac74A03Eb5bB60bDf2A3C9e142cD7DD36b7E":"14.939041958663", + "0x7E3A74AB669d4C5f411940e97d1c29db3D39e950":"0.004734138658", + "0x7F677cFf258702DDb349d227E46991D5A3277B4D":"0.042788724257", + "0x7aa7a54D58C8b2F2a8559301C880d3cEd10B7e55":"8.018979803865", + "0x7b0c025529F5E5f158fd1278415Eb156ED01E9D1":"100.162212997355", + "0x7bd0443062B9A17f9d088406aA0ac68511739f22":"1.711371948321", + "0x7bd82570bc838dEe6915B70059f554152C98A8b5":"1.462492851962", + "0x7bf623AD0f735c87cdD22D2781FE03C52CD082E0":"11.731295532037", + "0x7c6247667cf1bc912f57bC6AA7c125771fDDFCa9":"0.070327310177", + "0x7d3335fE8189F655F466603327d00D6ABBF6C441":"0.043490501166", + "0x7d935eA920F81c7d756F9Fce0A5639DCDb2E9839":"0.921239862745", + "0x7e98d97e0703a79299B65cB57a992a77a5079678":"14.527120219558", + "0x7f137a03bCF3125178b634689b575a746f4A1074":"0.650636996300", + "0x80A6C8163Dd15265346cddA2D32be8C132Be434F":"0.363143830810", + "0x8158b441c59E9271089EedD1163A08d4E0F557C8":"0.462151503648", + "0x81caBd4705fBe5701acd979DbEE416c6eEf31d4A":"1.673810386337", + "0x826ed9Fe9D60bc408d515BcFBC1E859b2d279b17":"20.111684855426", + "0x827Bb4fD138236ECFFe6458185e523b88C6492E9":"0.161824009200", + "0x833698FC59293EE9d10eCE3145dAAf4f0f3202E8":"0.087547817420", + "0x839B878873998F02cE2f5c6D78d1B0842e58F192":"1.839672390781", + "0x83A524af3cf8eB146132A2459664f7680A5515bE":"1.160925471628", + "0x83c0ED34e4B8d2a197b79A7eA401b9a37e1A90bC":"9.641298748881", + "0x83d6C448be2f9a6f770C07C673c360a389C70b8a":"2.390660480094", + "0x83eE25f205e3f5aaF98a10481FE2663D44D61180":"0.046278761139", + "0x84Da6e9Ff4569959d0aEe4BDf06DFDDa09c4fC52":"2.552016496676", + "0x84a042Cc18d15aF0b5a1bA7CA77C634f5cEf07A0":"8.486906343200", + "0x84a4FC0938089b7a864b8f1bE324737FCDB717b0":"0.007766909747", + "0x852E45eD1a6502406Af4c409DF4e0Eef3CC0A0E3":"0.098131484693", + "0x862606a6012fA68019EE26ce4A3b35Bc2684BEb4":"111.946435142369", + "0x864d69e84BCBf88dc63c0333501B1db5D3fDBf28":"4.732716927719", + "0x86526f570cA6c2C9FFb4A535fFe50fA56fD9Dd87":"16.712545918339", + "0x86bDf1314dD3BF4d407dBAd7A2c43261916708d7":"1.004226172939", + "0x872487cc815ff5a0Fb0871baA17333Fb0906306B":"0.166113777415", + "0x8740D9eC65b40bE5EbB84BD22607e81260fe3a3a":"27.304783287035", + "0x876B71aA426FAD5127910D3CC792e818B8229CF1":"4.293783156825", + "0x879c138C6dAF7f7637c49F1FB584b9338F9beC11":"0.720786275402", + "0x87fC1313880d579039aC48dB8B25428ed5F33C4a":"0.121344262220", + "0x88DdD3103a8025B8C97A9eaBea22005716Cf5e63":"28.314615931593", + "0x8A47eE6008aB47174a90019413746309eF6D13DC":"0.550667423647", + "0x8B2856fBD13D98B30c7bd47114284Dffb7d223c9":"2.438584446383", + "0x8B947a3f891B0eb514372689f40737B76c9eAf80":"36.551360870671", + "0x8BD2C9442dB9b0B23B1Fa646bC12A76C7f673317":"0.838875066599", + "0x8CBA226c4a94CDFe08f5711bB870459bc1abe0eb":"30.421595066821", + "0x8CbF96319b3C56d50a7C82EFb6d3c46bD6f889Ba":"0.002316752078", + "0x8Ce3d6bf7578df7B44311ce4a8fd5d3d76FEbfBC":"0.909264972756", + "0x8DE3c3891268502F77DB7E876d727257DEc0F852":"0.006621114321", + "0x8E2eC6F44dBC0C9d831c9C444246aB724a2E6A84":"36.979039593706", + "0x8E6D33902F45357567e55b59C5a74385547E00Ba":"0.027131163585", + "0x8F3aA7B8F863c26c5669830B2Fe013a9A6d60BBE":"1.075512281226", + "0x8F3eb3Aaa780f48f73D94d749A67B66ca867BaE0":"0.017914079257", + "0x8baEcC3F400FfA29dc191cfcE0c32c3e10Da818f":"0.019455962207", + "0x8c1dbaB9eC220A3F2bb16AD745A8ED3808a88963":"0.005617600169", + "0x8cb3b23568877583Db17879CC1A5dcC397A328FC":"9.263456078006", + "0x8d6e4d3cD07342BF8006f962Bd8B67C752568334":"7.921409761908", + "0x8dA4b8ae83EA2Fcc7bdBAf9B6042B819CD2DaE3c":"4.058082232425", + "0x8e5aFAA162F224381195fBDC24787CAf1B4d24C8":"15.918457305764", + "0x8f09F6be60bCD68fdfD437aa7A826627F6704c26":"50.803123700220", + "0x9009Fea7aAB817F3272F9Aa8bbAbcd7C818a8F60":"0.007501833107", + "0x9083dc6451E1be548918D85b571576399F01FFC1":"0.033165479991", + "0x90F15E09B8Fb5BC080B968170C638920Db3A3446":"0.013142216023", + "0x90aBCf1598ed3077861bCFb3B11EFcd1D7277223":"424.461165640172", + "0x9161AA3B1B1cac84f72a2B8610a173d69b5CFE41":"6.248224540755", + "0x92656aD77da2e82731d3cc083116960Ce29DfD74":"0.016269567505", + "0x926Dc3566F2563a8541968Ddd40180C9061FC06D":"0.475453829829", + "0x92cfE98Ea050A12d237eDb9b7901a15AaC88379A":"0.021533986229", + "0x931867863Be460131f305ebce2feEe83B839d5D4":"0.710773140545", + "0x9363080072da9510A469896E438A999a2C1Ca1f7":"10.641340318489", + "0x938bf2B8A7E40472D846e50b8ffE763A0fc749d4":"0.578850993562", + "0x9397E20E9DAb0A814305Cf2953552C78A1E7bE0D":"0.420771715102", + "0x93b52eBcBa12cFE758f13285147dbc3b43a9896D":"358.701676278252", + "0x943d616A6f0C973d93E08efa2501f935502B4B03":"7.310213376734", + "0x951b7393A03805573cFE0466682e676eb591834A":"0.262019640563", + "0x95715f56D9fAf221a848CA6dA11BC603C53DC95E":"6.750408748439", + "0x9582608416Ec5F854d2eD7e76600d175B5d23198":"20.951234875869", + "0x95F8D24cF78Dd25ce2FD9C210B18E58a972342a3":"3.173975338979", + "0x95d944aB6B4C53adCB9D71245923526143D54c3d":"0.186378672524", + "0x963eb0638A2FE075269b4E83F886e89d855ea4A8":"0.485430530860", + "0x976FdC5DfA145E3cbc690E9fef4a408642732952":"0.824528099752", + "0x97A29acC9E0E746f7c17BC545e8DE506db759ECd":"0.347206843901", + "0x97Ebf22E3eD3ff6573636D4aFEF82Ea0e4dA9127":"79.799825882465", + "0x9814C2c721244Af99dbcc314Bd9F53cE4DD905fE":"0.339031677822", + "0x9833Eee8055c425375DAdeB12109C4b3bA667d79":"3.608637778392", + "0x991EC11010d380002E1CE66D3860aEE12f54629F":"5.697079863776", + "0x9936bffD822149Cf9cf0722547FA720159Dc5fDa":"160.339845189939", + "0x99C0E6927F11845F13CC09c641748A4849203615":"1.046251033087", + "0x99E6dcB5f7058c2c3A8bECBEF2A257e392Fb807a":"0.367695772100", + "0x9A85548583ac34C367650518471D07301A32f11f":"0.022793364554", + "0x9C0790Eb0F96B16Ea1806e20B0D0E21A31DC93BC":"0.126385234412", + "0x9C21ad12A38b68259A545baCDc7B3BE42a580976":"2.718307880589", + "0x9Cf75595C8F3470Cd147935de2978E892E8D24a7":"4.387114669711", + "0x9D32F1e7C5fF707e92b4B30937A1e5E2C2De2462":"0.373680600196", + "0x9Dc6c0166CAf2dd3aB77d61D6DFb52a4504310b7":"0.035490359325", + "0x9E8c8e6Af3d811ce281b0e6889c57e8b7A772786":"11.426196223153", + "0x9F6C47623d23FC9dBCa3eD670a43792DB3aacaeE":"0.007618676328", + "0x9a141CF0507c1bE4380552FDB95C50C24b259ea3":"25.446153105304", + "0x9a4773EeEE73e34e1EE0E9A64E4b7453b0b04246":"7.829601054942", + "0x9b181869d98f707d931acc508935602d40EA70C3":"0.006996407958", + "0x9b2ca0B27415ab8ffA9d7FF2065C9bdE53462f46":"0.065452463312", + "0x9c4b88fBE06b6B1E461d9D294ecB7FB0c841dB4f":"2.037635487172", + "0x9cA8A0f39230D3a478B78617201A035c6d23Fbb0":"11.540129938090", + "0x9d5aB745dA91149f45E083FEe7960eba820209EA":"0.274545783790", + "0x9d653D68DA8d218Cd963bB051F17228Bd51Ea070":"0.967032157978", + "0x9fA10388D2BB8b7d8a2c8586772876D6302D4cB3":"4.853914830366", + "0xA07D83334758Af046DbCEe6991E341C0E061d121":"0.908149615240", + "0xA2ae59055e4B0e73A7e3F1A208062De2AEDee9be":"3.226950740955", + "0xA3a008b6f2BF51D4fc0Ab32Aa6b2a41c3A179833":"0.065180796330", + "0xA3a62540b6220631dB7776c83274de615d9aF7A9":"0.018161224616", + "0xA3dFB42724734D74d1B66f6F12a4AfA5D7D38DF2":"16.863559969547", + "0xA535d4751d3f6A83df1Fff7C787b9ac396E93a20":"44.947946643471", + "0xA5F3B718A76b22659aC8cb34A301f557926619c8":"0.593507359773", + "0xA7337bd8E6dD5134f3Af68a97C1f73Ca29523C89":"2.352009983430", + "0xA7B7E7f12b6Fa37b6FCC6269E2B46d4b241b4A61":"0.277249672746", + "0xABEBdB7d35f38D519d1C3ED72d3975424F420417":"1.796043176781", + "0xABf28EF8fC76326f89765609310CCbFCca291d19":"0.026214865848", + "0xADC031616771eCB503C65e607C13dDe152d459df":"4.648614252186", + "0xADe6BCF3Cf1509c35F7e9bbF8E7a2904DE276147":"0.000045076534", + "0xAE80890253a09F5196f9e0B843cF32dEF399C92c":"352.604663240461", + "0xAbD4c34Cc6BB50f49B633Ed82eA9a3bb44CBBCEC":"0.204707897159", + "0xAe053731E3b8705d3FB245D24d0300f6993a871B":"0.005974217716", + "0xAe6755901Dc6c8fe6cb544A73693eDbFd0d3D95B":"1.048606945159", + "0xB1EEF097cFEB4009Cebc3af3DAe3187D6ce57FFF":"106.866782735195", + "0xB2c7FE39cC240453CEA4cE03067d67b5fD049e20":"0.151583345744", + "0xB2d6Cc7098C3AecA089b6580A44b34559cd40971":"3.333069304398", + "0xB34F12a1484999a67935d3D2a85080286B3bd855":"0.012804960551", + "0xB4Bd807C9cDde19AE1498c7b7006713268E25997":"2.074504912977", + "0xB5823C3b01b3CaB24206C6Eb0aE1BaE3fF33cA79":"12.835498070165", + "0xB58CEb3969C267ab823435D7b5690CE02c9617e4":"21.128753959818", + "0xB618D796bD7eD78387C7dE0e08BB9868e98fcDCb":"0.001546263934", + "0xB677402A48A017B1A310EC58D59e00d4Ebdaa773":"0.028085429585", + "0xB8e34DcB781Bf6FA75b8d48d19c429638a98F284":"1.589957231552", + "0xB9542F9e7AB332346C818483bb8f3292a0653aB7":"0.780160658130", + "0xBBCF3985Eb53c30a8eEA01d9357314d61fE0e727":"0.018841016781", + "0xBBdB1d3fBC6AeD22B127A3C4E1cc137633eC6D03":"0.352146693614", + "0xBF39bF63bDC221e4d2c41FEB5353F223031492c9":"54.782792608771", + "0xBFa0095523d7e2e76b30246a66Ec3481f488599e":"17.918714210360", + "0xBaec9fc6cCe5789fFd5b9831223658e16352b303":"0.750881434761", + "0xBd5f593C86319523e6E8AfE60dEdBEfaeDBdB8C9":"0.072349349647", + "0xBe77a129Eb5eeF922A0867A38EFcb2B6Fa6C63E8":"6.994897427584", + "0xBe9e265c78a22e31d6a41Fc2710D9590ED2d5a96":"3.765639038999", + "0xBf47745b95463801A92031C50DAa68579734A89D":"0.000608350674", + "0xBf97Af0d72Df8F15D40726A3f6618346395F3E66":"43.389407825352", + "0xC06a89728621DA8D9EFE5e97E905E61CC6AE9377":"3.907141866592", + "0xC06dcd3F22a393ae29Ca3246FC01353742933378":"0.751127064725", + "0xC292EAFd5422A9c961CC2630CbDDf04c93Db33bD":"1.571172833971", + "0xC356E8fFEa6c866bE5F293d5FEe3A39c70e4075F":"42.966015183939", + "0xC40B5aeD750C297D6dFF8B8673b816342ED1e3DD":"0.456464056186", + "0xC480fA7FAF9E22b4756287770E9AF51716b0d335":"0.911151461242", + "0xC5856751806C729d0D9ECc6953c6eD23ADD16D64":"0.084711178072", + "0xC5BCC6C6da4996f1351d2F51c9570B5F60821c8F":"12.258768226632", + "0xC69aDcA4485C0C494f34e9Aa6b0A31f343f76411":"5.686812210029", + "0xC7719Ed61cc102cc0472E3739055B07857ed2DC2":"0.559798649800", + "0xC7F3A66f240B174cD4687854BC5cB90b592A58d2":"1.130671219758", + "0xC9420C9349120CA88D6C349CDF4Af038e2Ca5027":"61.652900607239", + "0xCB02B811A76C834ae74A47964F2059801c7583aE":"0.800147472324", + "0xCB2A9c2CE7e3d060700d72Cfe5E6d2BB9959f2f4":"4.184296168917", + "0xCCC3ac360ffaf97fFd1fD274C60DA04DeBEbe099":"0.381029304074", + "0xCCE97ecbA396Ecd63433677fe7D38C01CC5acB8c":"0.009053586454", + "0xCD0e675a32ebcE86c78D1fE9B1E406899Ca7F8FD":"0.340973467526", + "0xCDb0884CdAB00353dBCF8874478d16d99Ae35A28":"6.963918998136", + "0xCc686ea15d3529b5ef395110004A45985f156D7C":"0.154124775614", + "0xCcD1B2D70C4d1eFB25C527Ccce8cC8BDfe7c5Ea2":"0.568118827788", + "0xCd5eadBC411Aa119C00a6836cc680294be49F779":"0.763829329122", + "0xCe83Ef147d12bDC4E129C4327577A180ef520bfD":"0.144355675915", + "0xCeBba4E9f558716315FA543Ffcee29444484dC30":"25.908151309229", + "0xD004E55a84964d82341eE2ae58476de7D867Bc16":"0.477255267993", + "0xD0d919780117d4F539887f393d3a9188d91A9022":"39.160028391301", + "0xD19a40c9ad255ac0ea9F771f0D6f08D0A84F1554":"4.121003730355", + "0xD1B9dc34dcb85e970DcCAAC6Dd79a3810B9b4bF9":"0.057825796159", + "0xD1ccC963eF8119D935EdEB42e651f0cd81B83B77":"0.390458921828", + "0xD22581871a3f9908568124d9a50EAA1bC122117C":"3.590746148424", + "0xD27C03889e4D9024843f49EaB4F8B21a5c8b1b69":"0.888332135403", + "0xD663eB208f69Dd8e939327472FEddD3E9F58B34b":"699.576529403892", + "0xD9801B940A30908f66DF2E86c4f430BCD8178a02":"0.019600670627", + "0xD9f7824Cc0470BFB29E712096E11038BBC48482C":"0.143120672514", + "0xDAEB37717B0503f61390426FCD381DA237B0FB35":"0.001590629733", + "0xDC2E88dD9bDF3Dc44636C7CCAC86AC6DbAE0a8b3":"7.022573552219", + "0xDF5b8C2725C69aD86BaCAaA6EB17239175B9c4A9":"2.248820103653", + "0xDFad3536D95643533af6E1b0fFdeF9784BDccE88":"1.963131225030", + "0xDaA00b17594bfB23fF2d6c579C57E7B220d2bab1":"0.199527893055", + "0xDb965BBAD97d0784Afc22A5D82a24c5478fAf7F4":"1.288280356087", + "0xDeb9bC9527B27619dB786BF0DA026D60c6F53C95":"9.012484262579", + "0xE0b458936fAa9BeeA9a8673372e25B63cD5E7543":"1.866005660583", + "0xE14a13b8eB93B6569a748EA57A1A97025fc82BE9":"0.000015055786", + "0xE1AB85E458ebEA43c5302CE84f5a89Cb22fE351a":"7.868930503422", + "0xE23756866b46A6666D3FF9febE3d19c3868f22E1":"25.482396574762", + "0xE41103268C3AF5D99F225aaF58D4406290A8486F":"0.087771984649", + "0xE45D85B382EFd7833Da1B8CAB53B203D22340b1a":"29.741931838160", + "0xE4da32444226Ab63b1F8E9B7322561b6E9314Fc9":"1.199067456144", + "0xE50C51c11C30ad77711461ba46971BA6ED3DEB36":"8.478512890525", + "0xE5A3D77D7788A83f3549dD7F08Fc2aCed7E262d2":"199.905315849958", + "0xE5FaCAEF11b03766cf44A9bf629F551FA2355E54":"61.649427512391", + "0xE60458f765bc61e78940c5A275e9523D1f049690":"69.584886879225", + "0xE6368DE4335F10D55d64BB879B7f3D151F8825Dd":"0.008669188103", + "0xE7294495206c1F5223D7F0C59B004Ce971760541":"0.344667428563", + "0xE76Be9C1e10910d6Bc6b63D8031729747910c2f6":"9.138437492446", + "0xE7BCa65ade947Bd973078D3b8886A123174EA493":"0.468158103445", + "0xE805Ff9b9bf7fbfd9EbE13379fC8E470025da0C7":"4.138655493363", + "0xE85A3ec4CC1efB0069c638e57216b8fC478be6D6":"4.470539754310", + "0xE96379687267Fe3E1b90b3395AF219A491672b6b":"3.049815734680", + "0xE995ee3C79F078aDDA7ddc01e804C3B4Bb0D840c":"0.630878592061", + "0xEAC2C3d4f65CcE2Cb9197221934C04a4d1fEb863":"7.073773943368", + "0xEBf56B07485688Bf467e7C048EfE98922156b811":"11.532643400077", + "0xEe2319094bBB69c3DF5f1d2E5Ef62fD2F20030a1":"6.847595494476", + "0xEf80c44d996EbAC95aA62589986fb3FCDFE3f7d9":"14.176360913325", + "0xF02a31f977b52801322ec4891B1981CBAE182d3b":"2.907125872534", + "0xF02a5A6d2A16C7322f413059530FFAf2446AbC8f":"115.864966857487", + "0xF3531893C505392bD5E0719729FC53999aB83491":"0.025277103633", + "0xF42bE2Aa2e0B455dF33164Df69d8e8B9c6308cC0":"0.002044559869", + "0xF444A6B13c63999639dF53c1DB10bD59e3048b3e":"0.405734821421", + "0xF4de53a234DfA4A16feE8e0e01FE1CDD53b53232":"0.140675281729", + "0xF60fB06B540609Cc13F954e164d177dFCc1954F1":"0.001858823052", + "0xF77302872064A27a50df43AD1f397A0082567dd6":"5.995114264601", + "0xF7B18e107eb36797f4cE36dE756630B9C30969ad":"21.349926629753", + "0xF913d7846fdf67CFe638988b239Ea76f2e081103":"0.007312761454", + "0xF99403A51BD63f1101967EA8087acd5f68986096":"5.715636084602", + "0xFBA206E73709F614E5a85afF27A98692d4F3c579":"21.989486624873", + "0xFE4F9b23A2Cfe38D022D27C18376bAb9256E023A":"8.629432600457", + "0xFF16d64179A02D6a56a1183A28f1D6293646E2dd":"16.161024599872", + "0xFd543d0250ca20eb92bD296A79e9E5CEf1c84fb6":"2.524000217433", + "0xFe399dbDF75BCDbA1ff51CA88Bca6F2DCa6d458b":"1.506415888089", + "0xa06aaA0ba0b3Bb7cF89eE2D1C82d3E0bcbD75e19":"12.635852815481", + "0xa0a9996D2108F4a1F0dc7D82fa652eAC4425fA40":"3.257823724177", + "0xa18040cf6f97b4fFA8eA3d81F0788FEd88BFC988":"15.459022722241", + "0xa184c0A1eA94B409672469C12d9ee0D1aA13aE37":"4.995726310721", + "0xa2DAA4eED9BD8B2a23EDB163e6C31241368DCC43":"6.320722577059", + "0xa2E63faacE0e572DF34A1D283E272995e220b028":"0.021658477530", + "0xa36A09415e8Da9373c7117e5B4e3bC15b803459C":"72.346112224468", + "0xa3FC7fa34aCAeAd9aE7d19622dF5E58850450564":"1.215899548129", + "0xa42c2bAB6AC560db8E59a9C5aA926166C4D4c582":"1.068160601308", + "0xa4507877aEa6fF504aBC99c256192e78F5124274":"18.254369507720", + "0xa5ed8DA239f400141427800DA33B602A039F2254":"0.003532668656", + "0xa6EB9c5B8Be18A1015EE6BF3698d927b982B1676":"0.213948546299", + "0xa6ab5ca03954E8B2bb54e9006efb8e68824271Fa":"34.734759983196", + "0xa6dfBEbf6F3Ae1C7c46E60A1d353387eDeCe70DF":"45.801582491297", + "0xa7AdC93cAA4c0e1E0461483EB6ec79C9D4e3AD82":"58.791885542980", + "0xa946BeB2201c09B746185f75147C6087243D8396":"0.018071274247", + "0xa96704E2fb3Bf5Ca0e974baA4E1F5D938b2Ec934":"0.490888427701", + "0xa97E5Bff61454486b545538aCD574e664502B7ba":"0.420110275987", + "0xa9eAEF87c01B4C46a691862c7Ba94401394B8B9c":"0.059132475764", + "0xaB3219E1a492e14Ef7a35Da04D31Ed592bfC9bB4":"0.399989025164", + "0xaB43805bD19c5068F5C30bC4E5F187E2bB7258Be":"7.617075515057", + "0xaB96Cb3351582398616868f235958B068bce2881":"0.004581621519", + "0xaD7Bcc6a16eEF9BB537bc5a7bF1D7241a5ac744b":"0.074609020980", + "0xaDc8B15a7bD8d3b4B73708bC839bE2fd2e5B05b1":"10.936671711714", + "0xaE6C672d638a741FD6F8475300952DFd31Ed8bE5":"0.273242888578", + "0xaE7853A2AB2d63E2D9804A4Dd848A4C7f035eBFf":"0.321638921801", + "0xaa55b756Cc30EebB2728Fe5d43d334625e0A0b4c":"14.188257497345", + "0xae776034544F4CfaeaA1108fB252Bc0B69a19fEb":"0.000733116505", + "0xae77b94a122Ce1d0cD714FE27ddef8652A37C96d":"0.155190979182", + "0xaea8658914cf1EE552E30c8a71906cba5897b4AD":"97.321551298320", + "0xb04180e0d414E51e7a775944f1A6BA20BF955FB0":"3.943229706139", + "0xb05C61d0c80a7D7C8D7117626BEFaa034e80EaB7":"0.636921921132", + "0xb0C6d7085fA8cCC6c174841495eb700130eA2e97":"0.278194512815", + "0xb0eF8441B79048C37CF3BF9645762673518a74E0":"1.617147287781", + "0xb181AF0c0Eaf08Bc3160Aeb653c867eeE87e68dd":"6.848321422787", + "0xb222B456AD71e15f3E25e534028223d8c34e1159":"0.474446080604", + "0xb4633912c7374cd94D8917a167449e23b46d0AD1":"0.005551301481", + "0xb4aEED235917ac89CA283e9CAA51E8A1325cb62a":"42.093744114335", + "0xb4e7419A42EA01c418998FCc1df2ac0788e97566":"7.223748475387", + "0xb672246188E98faE363814E6d2566E3058F7ca46":"2.321576172697", + "0xb8110d5a1C8dF81f1aB2b717183ddB231CC0A513":"17.180876905766", + "0xb9f6C43FaAe4B3bC0e00C4918C17B86Fc9f84C66":"6.430460252924", + "0xbA2091Ab06210533d1a87B775Af10733C08c0A1E":"1.043099518189", + "0xbAC8075dB42624F0FC9d81E02942fF7c85f9A7D5":"0.489183734212", + "0xbB45a76388f7d9148A39651290aECA5dB3738798":"8.800814424014", + "0xbCC5C7180155a055bF87d97531c4584D51074c8e":"9.776087859180", + "0xbb3E24b2d16dcbb0DF7F663FDc8a83A914bBC017":"1.536894434186", + "0xbf0dAA1a4b9D9EEa898FD098Efa807f0d0C6B3f9":"0.040204039130", + "0xc138BdFFAf1f9003283D33C3E3F35D996ADB1fDe":"0.021940078150", + "0xc1919Db0c4D778693188c986462d351B2693Aa9a":"1.909367690459", + "0xc25fef376784E9BcaD3E1472575c1E10079c56d1":"6.347374062407", + "0xc4C57c2d1D6bBe6575F7839861252D3CBB79E96c":"9.371333778387", + "0xc5103C0a9e79C238e6dB7B0Eb8C22967723156e4":"0.404795006820", + "0xc674fFaD8082Aa238F15cd5a91aB1fd68aFEcEaE":"2.209775938771", + "0xc7457ebeD2706658ED33E3e28D5FecE19D77F9bf":"0.018154125058", + "0xc7B009237cB09690A4C887e9d4dc233f6F1e9e83":"7.004881105679", + "0xc7c61CEf75bf1450E7c69698A6c67348dD72aa55":"1.315389696849", + "0xc865B4ECDbAB1d7a369d68b7627e1822468F680A":"5.572610106792", + "0xc875F257fae93BB9Eb664B8b51057299d1527BB4":"5.740225936742", + "0xc92B444E0BC428459FdA180d71058164fd5e0E10":"0.079815731844", + "0xc938421f4F283ab7886f172413B3ABa23988ae41":"0.984437584975", + "0xcAFA7a1Ac382B4127AC85E270dAA6DfA3175EB1a":"0.043453228370", + "0xcC202930867769A83B61cf5053b65D1845E76Aea":"191.386176048477", + "0xcCBBf49F910a1147c6D9464B1497Caea6E9E3d36":"0.035171895265", + "0xcb47A044793227A943709B4F24BFcF6e01f1134C":"0.004853521426", + "0xcb59b8a261bBB6FfCb376b4c198BAA0bb896e6D6":"338.429214279150", + "0xcc08623800A8Dc53014FA5721E3d61fba399d9a6":"0.067460284500", + "0xcdE1e03C2aA571Ea02488698029b447F02e192D5":"0.045555694074", + "0xcdeE3aaDeb1dAA89a415eB164737315026782af0":"7.452510516304", + "0xd14e9D40278B9ce3daA887414CD5772e847E2bfa":"1.219620300140", + "0xd16b73d9bE64A01af0200d4C927B4A78b00C7F59":"0.210111452029", + "0xd185a341E2D30df901D4792F52af0265b45D7D9c":"34.640302682290", + "0xd292D267fEF81800e9841656cf7efa72202C35B3":"0.001621931487", + "0xd300c4D11702F5E726590857250bcab5b927C7B7":"1.571182089112", + "0xd334533980C201c1b93e39bBE6bD1E04be5C0f5D":"20.566202662128", + "0xd3c427D2b51854b8B21fEFEC0ca362454C23d365":"1.657588797241", + "0xd76A9CBA16b91f4a5927BD496F20De108ff445A7":"0.272648193026", + "0xd78E373e32bEC22D42919D471a442375A28A7094":"0.965356836678", + "0xd861415F6703ab50Ce101C7E6f6A80ada1FC2B1c":"0.860868288643", + "0xd9020D30AC868463c8B079430A2938263C43Fe90":"0.726662159853", + "0xd96A1769FdcB59DF2f5A18f91dDF6cCCd7002516":"0.061937289596", + "0xd987de72bFA8bd1fc76F7c7051EefBD72031BC1D":"3.388327238650", + "0xd9898Fe5707936874316023d74aE9d723f31fb3A":"4.087898588652", + "0xdCD88f18ba1c11AE9CE86763cbf6FEB475544790":"11.834647192773", + "0xdEc79CEE4397202739ED948ef98846774FEb53f9":"7.311156251117", + "0xdF20782fcF2Ec432ADb2C79af4EAD5ECf49c52cD":"1.945160736638", + "0xdF33800A566f993ea50c409a5D85B7170b60C0FD":"0.174360131139", + "0xdb8b6CE8D269ff5Cf362D1F2787a9Ae57084b025":"0.103556666487", + "0xdbF156853b040431A3e5d6D0802800B29E6EBF99":"48.763601566201", + "0xde7e8fB53E3Eba9D49cB0bFbB9A93AC63Aed07be":"23.576758328741", + "0xe07E487d5A5E1098BBb4d259DaC5EF83Ae273f4e":"36.441018859510", + "0xe0Ecce6515f8b366aa120480C231AaAFda4B21d5":"0.145640406154", + "0xe258Ae3539df97b115BA0Ea962544C1C3827a12a":"0.144119710041", + "0xe76D4165967fB1ed8D5a56d5DEa87e3bc8ECd3AC":"0.010093372158", + "0xe773b3DC818EBBcf13F224da7F17c86f87aFD0dA":"0.496230612859", + "0xe7afdc19099d9FAB3e76b4297F5E7D3B597a7CFB":"0.516086170142", + "0xe8ec1272503a63b57A02dc808E4d41D2BBD22E9A":"5.578912207470", + "0xe919047aA85E80cC19e619a081Dc4dd5620803af":"0.270944818050", + "0xe99D360A3f00cE3E27713b643c508b04489843Fb":"0.020726821427", + "0xeA205A0ECCf2Ff6C2764d31b3AaA876c9d59d5f5":"0.044663765949", + "0xeA74bF7E0Bb562E2d97d11c5A2D94B0a900d03bD":"2.099936725179", + "0xeAa630449b584b7517318D046d78a4538c7E1FbA":"3.340752872754", + "0xeB312F4921aEbbE99faCaCFE92f22b942Cbd7599":"1.770512790561", + "0xeBE6D31A37410b0DA360EA08cfA5Da4841489B7d":"0.162198342860", + "0xeCE1477D3C350a42486D2DC802F6243E99409A41":"0.236488179433", + "0xebCFD08a0a9a389789CcB19Bd1ebBeAf91f4c134":"0.909149992964", + "0xeeb7c600722630Dd0b7698a23236E8465c1d2D79":"0.239702332497", + "0xf18adf71266411FF39FfC268843c9A64b3292d86":"19.698854608695", + "0xf1EFb5235308B6736c31532002Fd36fF4ea91782":"5.198731649217", + "0xf20878614c5F352B57Ad7B16785140e73e69D602":"0.258879280621", + "0xf32939c033830042a5F2A25eb051670267a08aaB":"7.494547217249", + "0xf3Fbb2c0A711529eF9b81fE59a5Ef5b8f1E0eB27":"0.012552417814", + "0xf453193221c1465c2E58315E01689b7088819256":"1.332518547731", + "0xf499884e8533F131Cb98bAA8aAC14252AeAf1d44":"8.899689496970", + "0xf7413bd1aBBfAa54A42393F65B71b9bF9E5401e1":"10.148660709722", + "0xf87809F81E30d59F7824066C19F74876f9254657":"1.123732983690", + "0xf94c9f7fA1DFc2A5c4b58814a6B4026E6d14abDf":"0.569270204206", + "0xfAa5EEF67676139a82A63a72499605C6B8ff76Dd":"0.009222326297", + "0xfDd3631ad0eE38c3F49A1eaAcd2324e430e29Bb6":"1.296285752896", + "0xfE36dee625234EAEdbaDE27C15405eB482b81f1D":"0.544457053997", + "0xfF5F0ADb47995347A00f60d1E644FBE6fc5d3d73":"1.998514519853", + "0xfa4a45D755eA1c2b72Dd581b3E05dde3bFc13fad":"1.076251147294", + "0xfb0A954fe9378b2418f59a94A4D41A3953F92cd8":"1.173971994397", + "0xfe5660f2e5bA3012c4e517831d23EB0F97188bb5":"31.075410492468" +} \ No newline at end of file diff --git a/tasks/deployBoostedVault.ts b/tasks/deployBoostedVault.ts index 2f84eac2..c80e5c69 100644 --- a/tasks/deployBoostedVault.ts +++ b/tasks/deployBoostedVault.ts @@ -1,10 +1,21 @@ import "ts-node/register" import "tsconfig-paths/register" import { task, types } from "hardhat/config" -import { DEAD_ADDRESS } from "@utils/constants" +import { DEAD_ADDRESS, ONE_DAY, ONE_WEEK } from "@utils/constants" import { params } from "./taskUtils" -import { AssetProxy__factory, BoostedVault__factory, BoostedDualVault__factory } from "../types/generated" +import { + AssetProxy__factory, + BoostedVault__factory, + BoostedDualVault__factory, + StakedToken__factory, + SignatureVerifier__factory, + GamifiedManager__factory, + PlatformTokenVendorFactory__factory, +} from "../types/generated" +import { getChain, getChainAddress, resolveAddress } from "./utils/networkAddressFactory" +import { getSignerAccount } from "./utils/signerFactory" +import { deployContract } from "./utils/deploy-utils" task("getBytecode-BoostedDualVault").setAction(async () => { const size = BoostedDualVault__factory.bytecode.length / 2 / 1000 @@ -74,4 +85,58 @@ task("BoostedVault.deploy", "Deploys a BoostedVault") }, ) +task("StakedToken.deploy", "Deploys a Staked Token behind a proxy") + .addParam("questSignerAddress", "Address of account that signs quests", undefined, params.address) + .addOptionalParam("rewardsToken", "Rewards token address", "MTA", types.string) + .addOptionalParam("name", "Staked Token name", "Voting MTA V2", types.string) + .addOptionalParam("symbol", "Staked Token symbol", "vMTA", types.string) + .setAction(async (taskArgs, hre) => { + const deployer = await getSignerAccount(hre, taskArgs.speed) + const chain = getChain(hre) + + const nexusAddress = getChainAddress("Nexus", chain) + const rewardsDistributorAddress = getChainAddress("RewardsDistributor", chain) + const rewardsTokenAddress = resolveAddress(taskArgs.rewardsToken, chain) + + let gamifiedManagerAddress = getChainAddress("GamifiedManager", chain) + if (!gamifiedManagerAddress) { + const gamifiedManager = await deployContract(new GamifiedManager__factory(deployer.signer), "GamifiedManager") + gamifiedManagerAddress = gamifiedManager.address + } + + let signatureVerifierAddress = getChainAddress("SignatureVerifier", chain) + if (!signatureVerifierAddress) { + const signatureVerifier = await deployContract(new SignatureVerifier__factory(deployer.signer), "SignatureVerifier") + signatureVerifierAddress = signatureVerifier.address + } + + let platformTokenVendorFactoryAddress = getChainAddress("PlatformTokenVendorFactory", chain) + if (!platformTokenVendorFactoryAddress) { + const platformTokenVendorFactory = await deployContract( + new PlatformTokenVendorFactory__factory(deployer.signer), + "PlatformTokenVendorFactory", + ) + platformTokenVendorFactoryAddress = platformTokenVendorFactory.address + } + + const stakedTokenLibraryAddresses = { + "contracts/governance/staking/GamifiedManager.sol:GamifiedManager": gamifiedManagerAddress, + "contracts/rewards/staking/PlatformTokenVendorFactory.sol:PlatformTokenVendorFactory": platformTokenVendorFactoryAddress, + "contracts/governance/staking/deps/SignatureVerifier.sol:SignatureVerifier": signatureVerifierAddress, + } + const stakedTokenFactory = new StakedToken__factory(stakedTokenLibraryAddresses, deployer.signer) + console.log(`Staked contract size ${StakedToken__factory.bytecode.length / 2} bytes`) + const stakedTokenImpl = await deployContract(stakedTokenFactory, "StakedToken", [ + taskArgs.questSignerAddress, + nexusAddress, + rewardsTokenAddress, + rewardsTokenAddress, + ONE_WEEK, + ONE_DAY.mul(2), + ]) + + const data = stakedTokenImpl.interface.encodeFunctionData("initialize", [taskArgs.name, taskArgs.symbol, rewardsDistributorAddress]) + await deployContract(new AssetProxy__factory(deployer.signer), "AssetProxy", [stakedTokenImpl.address, deployer.address, data]) + }) + export {} diff --git a/tasks/deployPolygon.ts b/tasks/deployPolygon.ts index 09ec2476..b16b8581 100644 --- a/tasks/deployPolygon.ts +++ b/tasks/deployPolygon.ts @@ -337,8 +337,8 @@ task("deploy-polly", "Deploys mUSD & System to a Polygon network") const massetLogic = await deployContract(new MassetLogic__factory(signer), "MassetLogic") const managerLib = await deployContract(new MassetManager__factory(signer), "MassetManager") const linkedAddress = { - __$6a4be19f34d71a078def5cee18ccebcd10$__: massetLogic.address, - __$3b19b776afde68cd758db0cae1b8e49f94$__: managerLib.address, + "contracts/masset/MassetLogic.sol:MassetLogic": massetLogic.address, + "contracts/masset/MassetManager.sol:MassetManager": managerLib.address, } // Deploy mUSD Masset diff --git a/tasks/ens.ts b/tasks/ens.ts new file mode 100644 index 00000000..8ef038a8 --- /dev/null +++ b/tasks/ens.ts @@ -0,0 +1,70 @@ +import { subtask, task, types } from "hardhat/config" +import { randomBytes } from "crypto" +import { ONE_YEAR } from "@utils/constants" +import { EnsEthRegistrarController__factory } from "types/generated/factories/EnsEthRegistrarController__factory" +import { getSigner } from "./utils/signerFactory" +import { logTxDetails } from "./utils/deploy-utils" +import { getChain, getChainAddress } from "./utils/networkAddressFactory" + +subtask("ens-commit", "Registers a commitment to claiming an ENS domain") + .addParam("domain", "Domain name without the .eth extension.", "mstable", types.string) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const chain = getChain(hre) + const signer = await getSigner(hre, taskArgs.speed) + const signerAddress = await signer.getAddress() + + const controllerAddress = getChainAddress("ENSRegistrarController", chain) + const controller = EnsEthRegistrarController__factory.connect(controllerAddress, signer) + + // Generate a random value to mask our commitment + const buf = randomBytes(32) + const secret = `0x${buf.toString("hex")}` + + const domainName = taskArgs.domain + const commitment = await controller.makeCommitment(domainName, signerAddress, secret) + console.log(`Comitting to ENS domain ${domainName}.eth with secret ${secret}, owner ${signerAddress} and commitment ${commitment}`) + + const tx = await controller.commit(commitment) + logTxDetails(tx, "commit") + }) +task("ens-commit").setAction(async (_, __, runSuper) => { + await runSuper() +}) + +subtask("ens-register", "Registers an ENS domain") + .addParam("domain", "Domain name without the .eth extension.", "mstable", types.string) + .addParam("secret", "Secret from the previous commit transaction", undefined, types.string) + .addOptionalParam("years", "Number of years to register the domain.", 1, types.int) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const chain = getChain(hre) + const signer = await getSigner(hre, taskArgs.speed) + const signerAddress = await signer.getAddress() + + const controllerAddress = getChainAddress("ENSRegistrarController", chain) + const resolveAddress = getChainAddress("ENSResolver", chain) + const controller = EnsEthRegistrarController__factory.connect(controllerAddress, signer) + + const domainName = taskArgs.domain + const { secret, years } = taskArgs + + console.log( + `Registering ENS domain ${domainName}.eth with secret ${secret}, ENS resolver ${resolveAddress}, owner ${signerAddress}`, + ) + const tx = await controller.registerWithConfig( + domainName, + signerAddress, + ONE_YEAR.mul(years), + secret, + resolveAddress, + signerAddress, + ) + + logTxDetails(tx, "registerWithConfig ") + }) +task("ens-register").setAction(async (_, __, runSuper) => { + await runSuper() +}) + +module.exports = {} diff --git a/tasks/feeder.ts b/tasks/feeder.ts index 5049c213..dfd187af 100644 --- a/tasks/feeder.ts +++ b/tasks/feeder.ts @@ -65,8 +65,8 @@ const getBalances = async ( const getFeederPool = (signer: Signer, contractAddress: string, chain = Chain.mainnet): FeederPool => { const linkedAddress = { - __$60670dd84d06e10bb8a5ac6f99a1c0890c$__: getChainAddress("FeederManager", chain), - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: getChainAddress("FeederLogic", chain), + "contracts/feeders/FeederLogic.sol:FeederLogic": getChainAddress("FeederLogic", chain), + "contracts/feeders/FeederManager.sol:FeederManager": getChainAddress("FeederManager", chain), } const feederPoolFactory = new FeederPool__factory(linkedAddress, signer) return feederPoolFactory.attach(contractAddress) diff --git a/tasks/ops.ts b/tasks/ops.ts index 80b1274b..042e29c6 100644 --- a/tasks/ops.ts +++ b/tasks/ops.ts @@ -1,24 +1,19 @@ import axios from "axios" -import { subtask, task, types } from "hardhat/config" +import { task, types } from "hardhat/config" import { IEjector__factory, PAaveIntegration__factory, PLiquidator__factory, SavingsManager__factory, - RewardsDistributor__factory, - StakingRewards__factory, - ERC20__factory, AssetProxy__factory, + StakedToken__factory, } from "types/generated" -import { RewardsDistributorEth__factory } from "types/generated/factories/RewardsDistributorEth__factory" -import { BN, simpleToExactAmount } from "@utils/math" -import { formatUnits } from "ethers/lib/utils" -import { Chain, PmUSD, PUSDC, tokens } from "./utils/tokens" -import { getSigner, getSignerAccount } from "./utils/signerFactory" +import { QuestType } from "types/stakedToken" +import { PmUSD, PUSDC, tokens } from "./utils/tokens" +import { getSigner } from "./utils/signerFactory" import { logTxDetails } from "./utils/deploy-utils" import { getChain, getChainAddress, resolveAddress } from "./utils/networkAddressFactory" -import { usdFormatter } from "./utils" -import { getAaveTokens, getAlcxTokens, getBlock, getBlockRange, getCompTokens } from "./utils/snap-utils" +import { getBlockRange } from "./utils/snap-utils" task("eject-stakers", "Ejects expired stakers from Meta staking contract (vMTA)") .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "average", types.string) @@ -104,123 +99,6 @@ task("polly-daily", "Runs the daily jobs against the contracts on Polygon mainne await logTxDetails(savingsManagerTx, "collectAndStreamInterest") }) -task("polly-stake-imusd", "Stakes imUSD into the v-imUSD vault on Polygon") - .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) - .setAction(async (taskArgs, hre) => { - const signer = await getSigner(hre, taskArgs.speed) - - const amount = simpleToExactAmount(20) - const imUSD = ERC20__factory.connect(PmUSD.savings, signer) - const tx1 = await imUSD.approve(PmUSD.vault, amount) - await logTxDetails(tx1, "Relay approves v-imUSD vault to transfer imUSD") - - const vault = StakingRewards__factory.connect(PmUSD.vault, signer) - - const tx2 = await vault["stake(uint256)"](amount) - await logTxDetails(tx2, `stake ${usdFormatter(amount)} imUSD in v-imUSD vault`) - }) - -subtask("dis-rewards", "Distributes MTA rewards to a mStable vault and 3rd party pools") - .addParam( - "vaultAssets", - "Comma separated list of token symbols for vault assets or contract names for pools with no spaces. eg mUSD,MTA,GUSD,UniswapV2-MTA/WETH", - undefined, - types.string, - ) - .addParam( - "mtaAmounts", - "Comma separated list of MTA amounts with no spaces. eg 23278.21,16966.51,30180.15,23324.25", - undefined, - types.string, - ) - .addOptionalParam( - "platformAmounts", - "Comma separated list of platform reward, eg WMATIC, amounts with no spaces. eg 20832,15000", - undefined, - types.string, - ) - .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) - .setAction(async (taskArgs, hre) => { - const signer = await getSigner(hre, taskArgs.speed) - const chain = getChain(hre) - - // Validate the comma separated params - const vaultSymbols = taskArgs.vaultAssets.split(",") - const mtaAmounts = taskArgs.mtaAmounts.split(",") - const platformAmounts = taskArgs.platformAmounts?.split(",") || [] - if (vaultSymbols.length === 0) throw Error(`Must be at least one vault asset or pool`) - if (vaultSymbols.length !== mtaAmounts.length) - throw Error( - `${vaultSymbols.length} vault assets ${taskArgs.vaultAssets} does not match the ${mtaAmounts.length} MTA amounts ${taskArgs.amounts}.`, - ) - if (chain === Chain.polygon && vaultSymbols.length !== platformAmounts.length) - throw Error( - `${vaultSymbols.length} vault assets ${taskArgs.vaultAssets} does not match the ${platformAmounts.length} platform amounts ${taskArgs.platformAmounts}.`, - ) - - // Resolve the vault addresses from the asset symbols - const vaultsAddresses = vaultSymbols.map((symbol) => resolveAddress(symbol, chain, "vault")) - - // Convert the MTA amounts to BN amounts to 18 decimal places - let mtaAmountsTotal = BN.from(0) - const mtaAmountsBN = mtaAmounts.map((amount) => { - const amountBN = simpleToExactAmount(amount) - mtaAmountsTotal = mtaAmountsTotal.add(amountBN) - return amountBN - }) - - // Convert the platform amounts to BN amounts to 18 decimal places - let platformAmountsTotal = BN.from(0) - const platformAmountsBN = platformAmounts.map((amount) => { - const amountBN = simpleToExactAmount(amount) - platformAmountsTotal = platformAmountsTotal.add(amountBN) - return amountBN - }) - - if (chain === Chain.mainnet) { - const rewardsDistributorAddress = getChainAddress("RewardsDistributor", chain) - const rewardsDistributor = RewardsDistributorEth__factory.connect(rewardsDistributorAddress, signer) - - const tx = await rewardsDistributor.distributeRewards(vaultsAddresses, mtaAmountsBN) - await logTxDetails( - tx, - `distribute ${formatUnits(mtaAmountsTotal)} MTA to ${vaultsAddresses.length} vaults or pools ${ - taskArgs.vaultAssets - } with MTA amounts ${taskArgs.mtaAmounts}`, - ) - } else if (chain === Chain.polygon) { - const rewardsDistributorAddress = getChainAddress("RewardsDistributor", chain) - const rewardsDistributor = RewardsDistributor__factory.connect(rewardsDistributorAddress, signer) - - const tx = await rewardsDistributor.distributeRewards(vaultsAddresses, mtaAmountsBN, platformAmountsBN) - await logTxDetails( - tx, - `distribute ${formatUnits(mtaAmountsTotal)} MTA and ${platformAmountsTotal} platform rewards to ${ - vaultsAddresses.length - } vaults or pools ${taskArgs.vaultAssets} with MTA amounts ${taskArgs.mtaAmounts} and platform amounts ${ - taskArgs.platformAmounts - }`, - ) - } - }) -task("dis-rewards").setAction(async (_, __, runSuper) => { - await runSuper() -}) - -task("rewards", "Get Compound and Aave platform reward tokens") - .addOptionalParam("block", "Block number to compare rates at. (default: current block)", 0, types.int) - .setAction(async (taskArgs, hre) => { - const signer = await getSigner(hre, taskArgs.speed) - - const block = await getBlock(hre.ethers, taskArgs.block) - - console.log(`\nGetting platform tokens at block ${block.blockNumber}, ${block.blockTime.toUTCString()}`) - - await getCompTokens(signer, block) - await getAaveTokens(signer, block) - await getAlcxTokens(signer, block) - }) - task("proxy-upgrades", "Proxy implementation changes") .addParam( "asset", @@ -232,7 +110,7 @@ task("proxy-upgrades", "Proxy implementation changes") .addOptionalParam("from", "Block to query transaction events from. (default: deployment block)", 10148031, types.int) .addOptionalParam("to", "Block to query transaction events to. (default: current block)", 0, types.int) .setAction(async (taskArgs, hre) => { - const signer = await getSigner(hre) + const signer = await getSigner(hre, taskArgs.speed) const asset = tokens.find((t) => t.symbol === taskArgs.asset) if (!asset) { @@ -253,84 +131,29 @@ task("proxy-upgrades", "Proxy implementation changes") }) }) -task("vault-stake", "Stake into a vault") - .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) - .addParam("amount", "Amount to be staked", undefined, types.float) +task("quest-add", "Adds a quest to the staked token") + .addParam("multiplier", "Quest multiplier. 1 = 1.01x or 1%, 10 = 1.1x or 10%", undefined, types.int, false) + .addParam("type", "Seasonal or permanent", "seasonal", types.string) + .addOptionalParam("pk", "Test private key", undefined, types.string) .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) .setAction(async (taskArgs, hre) => { - const signer = await getSigner(hre, taskArgs.speed) + const signer = await getSigner(hre, taskArgs.speed, taskArgs.pk) const chain = getChain(hre) - const signerAddress = await signer.getAddress() - - const assetSymbol = taskArgs.asset - const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) - if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) - if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) - - const vault = StakingRewards__factory.connect(assetToken.vault, signer) - - const amount = simpleToExactAmount(taskArgs.amount) - - const tx = await vault["stake(uint256)"](amount) - await logTxDetails(tx, `${signerAddress} stakes ${amount} ${assetSymbol} in vault`) - }) - -task("vault-withdraw", "Withdraw from a vault") - .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) - .addParam("amount", "Amount to be withdrawn", undefined, types.float) - .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) - .setAction(async (taskArgs, hre) => { - const chain = getChain(hre) - const signerAccount = await getSignerAccount(hre, taskArgs.speed) - - const assetSymbol = taskArgs.asset - const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) - if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) - if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) - - const vault = StakingRewards__factory.connect(assetToken.vault, signerAccount.signer) - - const amount = simpleToExactAmount(taskArgs.amount) - const tx = await vault.withdraw(amount) - await logTxDetails(tx, `${signerAccount.address} withdraw ${amount} ${assetSymbol} from vault`) - }) - -task("vault-exit", "Exit from vault claiming rewards") - .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) - .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) - .setAction(async (taskArgs, hre) => { - const chain = getChain(hre) - const signerAccount = await getSignerAccount(hre, taskArgs.speed) - - const assetSymbol = taskArgs.asset - const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) - if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) - if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) - - const vault = StakingRewards__factory.connect(assetToken.vault, signerAccount.signer) - - const tx = await vault.exit() - await logTxDetails(tx, `${signerAccount.address} exits ${assetSymbol} vault`) - }) - -task("vault-claim", "Claim rewards from vault") - .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) - .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) - .setAction(async (taskArgs, hre) => { - const chain = getChain(hre) - const signer = await getSigner(hre, taskArgs.speed) - const signerAddress = await signer.getAddress() - - const assetSymbol = taskArgs.asset - const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) - if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) - if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) - - const vault = StakingRewards__factory.connect(assetToken.vault, signer) + let type: QuestType + if (taskArgs.type === "seasonal" || taskArgs.type === "s") { + type = QuestType.SEASONAL + } else if (taskArgs.type === "permanent" || taskArgs.type === "p") { + type = QuestType.PERMANENT + } else { + throw Error(`Invalid quest type ${taskArgs.type}. Must be either: seasonal, s, permanent or p.`) + } - const tx = await vault.claimReward() - await logTxDetails(tx, `${signerAddress} claim rewards from ${assetSymbol} vault`) + const stakedTokenAddress = await resolveAddress("StakedToken", chain) + const stakedToken = StakedToken__factory.connect(stakedTokenAddress, signer) + const expiry = Math.floor(Date.now() / 1000) + const addQuestData = stakedToken.interface.encodeFunctionData("addQuest", [type, taskArgs.multiplier, expiry]) + console.log(`Destination ${stakedTokenAddress}, data: ${addQuestData}`) + // const tx = await stakedToken.addQuest(type, taskArgs.multiplier, expiry) + // await logTxDetails(tx, `Add ${taskArgs.type} quest with ${taskArgs.multiplier} multiplier`) }) - -module.exports = {} diff --git a/tasks/rewards.ts b/tasks/rewards.ts index ddd51c97..a568d380 100644 --- a/tasks/rewards.ts +++ b/tasks/rewards.ts @@ -1,8 +1,15 @@ /* eslint-disable no-restricted-syntax */ import { BN, simpleToExactAmount } from "@utils/math" -import { task, types } from "hardhat/config" -import rewardsFiles from "./balancer-mta-rewards/20200720.json" -import { usdFormatter } from "./utils" +import { subtask, task, types } from "hardhat/config" +import { RewardsDistributorEth__factory } from "types/generated/factories/RewardsDistributorEth__factory" +import { RewardsDistributor__factory } from "types/generated/factories/RewardsDistributor__factory" +import { formatUnits } from "ethers/lib/utils" +import { Liquidator__factory } from "types" +import rewardsFiles from "./balancer-mta-rewards/20210720.json" +import { Chain, logTxDetails, usdFormatter } from "./utils" +import { getAaveTokens, getAlcxTokens, getBlock, getCompTokens } from "./utils/snap-utils" +import { getSigner } from "./utils/signerFactory" +import { getChain, getChainAddress, resolveAddress, resolveToken } from "./utils/networkAddressFactory" task("sum-rewards", "Totals the rewards in a disperse json file") .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) @@ -19,3 +26,149 @@ task("sum-rewards", "Totals the rewards in a disperse json file") console.log(`Total ${usdFormatter(total)}`) console.log(`Count ${count}`) }) + +subtask("dis-rewards", "Distributes MTA rewards to a mStable vault and 3rd party pools") + .addParam( + "vaultAssets", + "Comma separated list of token symbols for vault assets or contract names for pools with no spaces. eg mUSD,MTA,GUSD,UniswapV2-MTA/WETH", + undefined, + types.string, + ) + .addParam( + "mtaAmounts", + "Comma separated list of MTA amounts with no spaces. eg 23278.21,16966.51,30180.15,23324.25", + undefined, + types.string, + ) + .addOptionalParam( + "platformAmounts", + "Comma separated list of platform reward, eg WMATIC, amounts with no spaces. eg 20832,15000", + undefined, + types.string, + ) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const signer = await getSigner(hre, taskArgs.speed) + const chain = getChain(hre) + + // Validate the comma separated params + const vaultSymbols = taskArgs.vaultAssets.split(",") + const mtaAmounts = taskArgs.mtaAmounts.split(",") + const platformAmounts = taskArgs.platformAmounts?.split(",") || [] + if (vaultSymbols.length === 0) throw Error(`Must be at least one vault asset or pool`) + if (vaultSymbols.length !== mtaAmounts.length) + throw Error( + `${vaultSymbols.length} vault assets ${taskArgs.vaultAssets} does not match the ${mtaAmounts.length} MTA amounts ${taskArgs.amounts}.`, + ) + if (chain === Chain.polygon && vaultSymbols.length !== platformAmounts.length) + throw Error( + `${vaultSymbols.length} vault assets ${taskArgs.vaultAssets} does not match the ${platformAmounts.length} platform amounts ${taskArgs.platformAmounts}.`, + ) + + // Resolve the vault addresses from the asset symbols + const vaultsAddresses = vaultSymbols.map((symbol) => resolveAddress(symbol, chain, "vault")) + + // Convert the MTA amounts to BN amounts to 18 decimal places + let mtaAmountsTotal = BN.from(0) + const mtaAmountsBN = mtaAmounts.map((amount) => { + const amountBN = simpleToExactAmount(amount) + mtaAmountsTotal = mtaAmountsTotal.add(amountBN) + return amountBN + }) + + // Convert the platform amounts to BN amounts to 18 decimal places + let platformAmountsTotal = BN.from(0) + const platformAmountsBN = platformAmounts.map((amount) => { + const amountBN = simpleToExactAmount(amount) + platformAmountsTotal = platformAmountsTotal.add(amountBN) + return amountBN + }) + + if (chain === Chain.mainnet) { + const rewardsDistributorAddress = getChainAddress("RewardsDistributor", chain) + const rewardsDistributor = RewardsDistributorEth__factory.connect(rewardsDistributorAddress, signer) + + const tx = await rewardsDistributor.distributeRewards(vaultsAddresses, mtaAmountsBN) + await logTxDetails( + tx, + `distribute ${formatUnits(mtaAmountsTotal)} MTA to ${vaultsAddresses.length} vaults or pools ${ + taskArgs.vaultAssets + } with MTA amounts ${taskArgs.mtaAmounts}`, + ) + } else if (chain === Chain.polygon) { + const rewardsDistributorAddress = getChainAddress("RewardsDistributor", chain) + const rewardsDistributor = RewardsDistributor__factory.connect(rewardsDistributorAddress, signer) + + const tx = await rewardsDistributor.distributeRewards(vaultsAddresses, mtaAmountsBN, platformAmountsBN) + await logTxDetails( + tx, + `distribute ${formatUnits(mtaAmountsTotal)} MTA and ${platformAmountsTotal} platform rewards to ${ + vaultsAddresses.length + } vaults or pools ${taskArgs.vaultAssets} with MTA amounts ${taskArgs.mtaAmounts} and platform amounts ${ + taskArgs.platformAmounts + }`, + ) + } + }) +task("dis-rewards").setAction(async (_, __, runSuper) => { + await runSuper() +}) + +task("rewards", "Get Compound and Aave platform reward tokens") + .addOptionalParam("block", "Block number to compare rates at. (default: current block)", 0, types.int) + .setAction(async (taskArgs, hre) => { + const signer = await getSigner(hre, taskArgs.speed) + + const block = await getBlock(hre.ethers, taskArgs.block) + + console.log(`\nGetting platform tokens at block ${block.blockNumber}, ${block.blockTime.toUTCString()}`) + + await getCompTokens(signer, block) + await getAaveTokens(signer, block) + await getAlcxTokens(signer, block) + }) + +task("liq-trig", "Triggers a liquidation of a integration contract") + .addParam( + "basset", + "Token symbol of bAsset that is integrated to a platform. eg USDC, WBTC, GUSD, alUSD", + undefined, + types.string, + false, + ) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const signer = await getSigner(hre, taskArgs.speed) + const chain = getChain(hre) + + const bAsset = await resolveToken(taskArgs.basset, chain, "integrator") + + const liquidatorAddress = await resolveAddress("Liquidator", chain) + const liquidator = Liquidator__factory.connect(liquidatorAddress, signer) + const tx = await liquidator.triggerLiquidation(bAsset.integrator) + await logTxDetails(tx, "trigger liquidation") + }) + +task("liq-trig-aave", "Triggers a liquidation of stkAAVE") + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const signer = await getSigner(hre, taskArgs.speed) + const chain = getChain(hre) + + const liquidatorAddress = await resolveAddress("Liquidator", chain) + const liquidator = Liquidator__factory.connect(liquidatorAddress, signer) + const tx1 = await liquidator.triggerLiquidationAave() + await logTxDetails(tx1, "trigger liquidation Aave") + }) + +task("liq-claim-aave", "Triggers a liquidation of stkAAVE") + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const signer = await getSigner(hre, taskArgs.speed) + const chain = getChain(hre) + + const liquidatorAddress = await resolveAddress("Liquidator", chain) + const liquidator = Liquidator__factory.connect(liquidatorAddress, signer) + const tx = await liquidator.claimStakedAave() + await logTxDetails(tx, "claim Aave") + }) diff --git a/tasks/utils/feederUtils.ts b/tasks/utils/feederUtils.ts index 698080c2..610ef41a 100644 --- a/tasks/utils/feederUtils.ts +++ b/tasks/utils/feederUtils.ts @@ -78,8 +78,8 @@ export const deployFeederPool = async (signer: Signer, feederData: FeederData, c // Invariant Validator const linkedAddress = { - __$60670dd84d06e10bb8a5ac6f99a1c0890c$__: feederManagerAddress, - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: feederLogicAddress, + "contracts/feeders/FeederLogic.sol:FeederLogic": feederLogicAddress, + "contracts/feeders/FeederManager.sol:FeederManager": feederManagerAddress, } const impl = await deployContract(new FeederPool__factory(linkedAddress, signer), "FeederPool", [ diff --git a/tasks/utils/networkAddressFactory.ts b/tasks/utils/networkAddressFactory.ts index 7dd6c6f3..69dac9a2 100644 --- a/tasks/utils/networkAddressFactory.ts +++ b/tasks/utils/networkAddressFactory.ts @@ -1,3 +1,4 @@ +import { DEAD_ADDRESS } from "@utils/constants" import { ethereumAddress } from "@utils/regex" import { AssetAddressTypes, Chain, Token, tokens } from "./tokens" @@ -23,6 +24,10 @@ export const contractNames = [ "FeederWrapper", "FeederInterestValidator", "BasketManager", // Legacy mUSD contract + "SignatureVerifier", + "GamifiedManager", + "StakedToken", + "PlatformTokenVendorFactory", "AaveIncentivesController", "AaveLendingPoolAddressProvider", "AlchemixStakingPool", @@ -34,6 +39,8 @@ export const contractNames = [ "UniswapV2-MTA/WETH", "MStableYieldSource", // Used for PoolTogether "OperationsSigner", + "ENSRegistrarController", + "ENSResolver", ] as const export type ContractNames = typeof contractNames[number] @@ -111,6 +118,10 @@ export const getChainAddress = (contractName: ContractNames, chain: Chain): stri return "0xdB4C9f763A4B13CF2830DFe7c2854dADf5b96E99" case "OperationsSigner": return "0xb81473f20818225302b8fffb905b53d58a793d84" + case "ENSRegistrarController": + return "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5" + case "ENSResolver": + return "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41" default: } } else if (chain === Chain.polygon) { @@ -173,6 +184,16 @@ export const getChainAddress = (contractName: ContractNames, chain: Chain): stri return "0xeD04Cd19f50F893792357eA53A549E23Baf3F6cB" case "DelayedProxyAdmin": return "0x2d369F83E9DC764a759a74e87a9Bc542a2BbfdF0" + case "SignatureVerifier": + return "0x5Ff8F862092AE846875e745126177fDc03D7234e" + case "GamifiedManager": + return "0xcD55Eb0Ce6Ee8eEf7b1f828a4f6F6538a3DEAB80" + case "StakedToken": + return "0x4d8E465ba7FACa907E8A5F39649e056bB14802D1" + case "PlatformTokenVendorFactory": + return "0xB1dd91A2ceac170122971fdA0Bb8Db9F5f9f03d1" + case "RewardsDistributor": + return DEAD_ADDRESS default: } } diff --git a/tasks/utils/signerFactory.ts b/tasks/utils/signerFactory.ts index dbf27262..6d613b0d 100644 --- a/tasks/utils/signerFactory.ts +++ b/tasks/utils/signerFactory.ts @@ -26,10 +26,16 @@ export const getDefenderSigner = async (speed: Speed = "fast"): Promise let signerInstance: Signer -export const getSigner = async (hre: HardhatRuntime = {}, speed: Speed = "fast"): Promise => { +export const getSigner = async (hre: HardhatRuntime = {}, speed: Speed = "fast", privateKey?: string): Promise => { // If already initiated a signer, just return the singleton instance if (signerInstance) return signerInstance + if (privateKey) { + const wallet = new Wallet(privateKey, hre.ethers.provider) + console.log(`Using signer ${await wallet.getAddress()} from private key`) + return wallet + } + // If connecting to a forked chain if (["tasks-fork.config.ts", "tasks-fork-polygon.config.ts"].includes(hre?.hardhatArguments.config)) { const chain = getChain(hre) diff --git a/tasks/utils/tokens.ts b/tasks/utils/tokens.ts index 201d23d0..bbcd12cf 100644 --- a/tasks/utils/tokens.ts +++ b/tasks/utils/tokens.ts @@ -313,6 +313,15 @@ export const PMTA: Token = { quantityFormatter: "USD", } +export const RMTA: Token = { + symbol: "RMTA", + address: "0x273bc479E5C21CAA15aA8538DecBF310981d14C0", + chain: Chain.ropsten, + decimals: 18, + quantityFormatter: "USD", + vault: "0x4d8E465ba7FACa907E8A5F39649e056bB14802D1", +} + export const PWMATIC: Token = { symbol: "PWMATIC", address: "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", @@ -364,6 +373,7 @@ export const cyMUSD: Token = { export const tokens = [ MTA, PMTA, + RMTA, mUSD, mBTC, sUSD, diff --git a/tasks/vault.ts b/tasks/vault.ts new file mode 100644 index 00000000..466c0643 --- /dev/null +++ b/tasks/vault.ts @@ -0,0 +1,87 @@ +import { task, types } from "hardhat/config" +import { StakingRewards__factory } from "types/generated" +import { simpleToExactAmount } from "@utils/math" +import { tokens } from "./utils/tokens" +import { getSignerAccount } from "./utils/signerFactory" +import { logTxDetails } from "./utils/deploy-utils" +import { getChain } from "./utils/networkAddressFactory" + +task("vault-stake", "Stake into a vault") + .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) + .addParam("amount", "Amount to be staked", undefined, types.float) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const chain = getChain(hre) + const signerAccount = await getSignerAccount(hre, taskArgs.speed) + + const assetSymbol = taskArgs.asset + const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) + if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) + if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) + + const vault = StakingRewards__factory.connect(assetToken.vault, signerAccount.signer) + + const amount = simpleToExactAmount(taskArgs.amount) + + const tx = await vault["stake(uint256)"](amount) + await logTxDetails(tx, `${signerAccount.address} stakes ${amount} ${assetSymbol} in vault`) + }) + +task("vault-withdraw", "Withdraw from a vault") + .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) + .addParam("amount", "Amount to be withdrawn", undefined, types.float) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const chain = getChain(hre) + const signerAccount = await getSignerAccount(hre, taskArgs.speed) + + const assetSymbol = taskArgs.asset + const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) + if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) + if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) + + const vault = StakingRewards__factory.connect(assetToken.vault, signerAccount.signer) + + const amount = simpleToExactAmount(taskArgs.amount) + + const tx = await vault.withdraw(amount) + await logTxDetails(tx, `${signerAccount.address} withdraw ${amount} ${assetSymbol} from vault`) + }) + +task("vault-exit", "Exit from vault claiming rewards") + .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const chain = getChain(hre) + const signerAccount = await getSignerAccount(hre, taskArgs.speed) + + const assetSymbol = taskArgs.asset + const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) + if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) + if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) + + const vault = StakingRewards__factory.connect(assetToken.vault, signerAccount.signer) + + const tx = await vault.exit() + await logTxDetails(tx, `${signerAccount.address} exits ${assetSymbol} vault`) + }) + +task("vault-claim", "Claim rewards from vault") + .addParam("asset", "Symbol of the asset that has a mStable vault. eg mUSD, alUSD, MTA", undefined, types.string) + .addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) + .setAction(async (taskArgs, hre) => { + const chain = getChain(hre) + const signerAccount = await getSignerAccount(hre, taskArgs.speed) + + const assetSymbol = taskArgs.asset + const assetToken = tokens.find((t) => t.symbol === assetSymbol && t.chain === chain) + if (!assetToken) throw Error(`Could not find asset with symbol ${assetSymbol}`) + if (!assetToken.vault) throw Error(`No vault is configured for asset ${assetSymbol}`) + + const vault = StakingRewards__factory.connect(assetToken.vault, signerAccount.signer) + + const tx = await vault.claimReward() + await logTxDetails(tx, `${signerAccount.address} claim rewards from ${assetSymbol} vault`) + }) + +module.exports = {} diff --git a/tasks/weekly.ts b/tasks/weekly.ts index 14b44e7d..c7a393d2 100644 --- a/tasks/weekly.ts +++ b/tasks/weekly.ts @@ -12,8 +12,19 @@ task("distribute-mta-mainnet", "Distributes MTA rewards on Mainnet") const signerAddress = await signer.getAddress() const rewardSymbol = MTA.symbol const ownerTokenType: AssetAddressTypes = "vault" - const vaultsOrPools: Array = [mUSD, mBTC, GUSD, BUSD, alUSD, HBTC, TBTC, MTA, "UniswapV2-MTA/WETH"] - const mtaAmounts = [22482.92, 9812.28, 49124.82, 22966.19, 28172.94, 14351.23, 14565.61, 40000, 5000] + const vaultsOrPools: Array = [MTA, "UniswapV2-MTA/WETH", mUSD, mBTC, HBTC, alUSD, BUSD, GUSD, TBTC] + const mtaAmounts = [40000, 5000, 21961.88, 10333.32, 27197.64, 15603.06, 21174.63, 48388.76, 16816.7] + const vaultNames = [ + "Staking V1 rewards", + "MTA / WETH Uniswap v2", + "imUSD Vault", + "imBTC Vault", + "HBTC Feeder Pool", + "alUSD Feeder Pool", + "BUSD Feeder Pool", + "GUSD Feeder Pool", + "TBTC Feeder Pool", + ] // Create a comma separated list of token symbols and amounts const symbolOrNames = vaultsOrPools.map((v) => { @@ -53,6 +64,11 @@ task("distribute-mta-mainnet", "Distributes MTA rewards on Mainnet") ownerTokenType, }) } + + console.log(`\nDiscord announcement`) + vaultNames.forEach((name, i) => { + console.log(`- ${name} ${mtaAmounts[i]} MTA`) + }) }) task("distribute-mta-polygon", "Distributes MTA and Matic rewards on Polygon") diff --git a/test-fork/feeders/feeders-deployment.spec.ts b/test-fork/feeders/feeders-deployment.spec.ts index 7298a931..5d9d0b3e 100644 --- a/test-fork/feeders/feeders-deployment.spec.ts +++ b/test-fork/feeders/feeders-deployment.spec.ts @@ -85,8 +85,8 @@ const deployFeederPool = async (sender: Signer, addresses: CommonAddresses, feed if (addresses.feederLogic && addresses.feederManager) { console.log(`Using FeederLogic ${addresses.feederLogic.address} and FeederManager ${addresses.feederManager.address}`) const linkedAddress = { - __$60670dd84d06e10bb8a5ac6f99a1c0890c$__: addresses.feederManager.address, - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: addresses.feederLogic.address, + "contracts/feeders/FeederLogic.sol:FeederLogic": addresses.feederLogic.address, + "contracts/feeders/FeederManager.sol:FeederManager": addresses.feederManager.address, } // Implementation feederPoolFactory = new FeederPool__factory(linkedAddress, sender) @@ -103,8 +103,8 @@ const deployFeederPool = async (sender: Signer, addresses: CommonAddresses, feed console.log(`Deployed FeederManager library to ${managerLib.address}. gas used ${receiptManager.gasUsed}`) const linkedAddress = { - __$60670dd84d06e10bb8a5ac6f99a1c0890c$__: managerLib.address, - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: feederLogic.address, + "contracts/feeders/FeederLogic.sol:FeederLogic": managerLib.address, + "contracts/feeders/FeederManager.sol:FeederManager": managerLib.address, } // Implementation feederPoolFactory = new FeederPool__factory(linkedAddress, sender) diff --git a/test-utils/machines/feederMachine.ts b/test-utils/machines/feederMachine.ts index b95da5a6..9ea43dc2 100644 --- a/test-utils/machines/feederMachine.ts +++ b/test-utils/machines/feederMachine.ts @@ -69,8 +69,8 @@ export class FeederMachine { const feederLogic = await new FeederLogic__factory(this.sa.default.signer).deploy() const feederManager = await new FeederManager__factory(this.sa.default.signer).deploy() const linkedAddress = { - __$60670dd84d06e10bb8a5ac6f99a1c0890c$__: feederManager.address, - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: feederLogic.address, + "contracts/feeders/FeederLogic.sol:FeederLogic": feederLogic.address, + "contracts/feeders/FeederManager.sol:FeederManager": feederManager.address, } // - Deploy InterestValidator contract diff --git a/test-utils/machines/mAssetMachine.ts b/test-utils/machines/mAssetMachine.ts index 22d8fdb8..f5539e63 100644 --- a/test-utils/machines/mAssetMachine.ts +++ b/test-utils/machines/mAssetMachine.ts @@ -69,8 +69,8 @@ export class MassetMachine { this.sa.mockInterestValidator.address, ) const mAssetFactoryLibs = { - __$6a4be19f34d71a078def5cee18ccebcd10$__: logicLib.address, - __$3b19b776afde68cd758db0cae1b8e49f94$__: managerLib.address, + "contracts/masset/MassetLogic.sol:MassetLogic": logicLib.address, + "contracts/masset/MassetManager.sol:MassetManager": managerLib.address, } const MassetFactory = new ExposedMasset__factory(mAssetFactoryLibs, this.sa.default.signer) const impl = (await MassetFactory.deploy(nexus.address, simpleToExactAmount(5, 13))) as ExposedMasset @@ -131,8 +131,8 @@ export class MassetMachine { // 3.2. Masset const mAssetFactoryLibs = { - __$6a4be19f34d71a078def5cee18ccebcd10$__: logicLib.address, - __$3b19b776afde68cd758db0cae1b8e49f94$__: managerLib.address, + "contracts/masset/MassetLogic.sol:MassetLogic": logicLib.address, + "contracts/masset/MassetManager.sol:MassetManager": managerLib.address, } const MassetFactory = new ExposedMasset__factory(mAssetFactoryLibs, this.sa.default.signer) const impl = (await MassetFactory.deploy(nexus.address, simpleToExactAmount(5, 13))) as Masset diff --git a/test/feeders/from-data/single.spec.ts b/test/feeders/from-data/single.spec.ts index 8656476b..5b78e992 100644 --- a/test/feeders/from-data/single.spec.ts +++ b/test/feeders/from-data/single.spec.ts @@ -52,7 +52,7 @@ describe("Feeder Validator - One basket one test", () => { sa = mAssetMachine.sa const logic = await new FeederLogic__factory(sa.default.signer).deploy() const linkedAddress = { - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: logic.address, + "contracts/feeders/FeederLogic.sol:FeederLogic": logic.address, } exposedFeeder = await new ExposedFeederLogic__factory(linkedAddress, sa.default.signer).deploy() }) @@ -248,7 +248,7 @@ describe("Feeder Validator - One basket one test", () => { ).connect(sa.default.signer) as FeederPool__factory const linkedAddress = { - __$7791d1d5b7ea16da359ce352a2ac3a881c$__: feederLogic.address, + "contracts/feeders/FeederLogic.sol:FeederLogic": feederLogic.address, } exposedFeeder = await new ExposedFeederLogic__factory(linkedAddress, sa.default.signer).deploy() }) diff --git a/test/feeders/redeem.spec.ts b/test/feeders/redeem.spec.ts index e2a71336..c73f2935 100644 --- a/test/feeders/redeem.spec.ts +++ b/test/feeders/redeem.spec.ts @@ -276,8 +276,11 @@ describe("Feeder - Redeem", () => { expect(redeemEvent.args.redeemer, "redeemer in RedeemedMulti event").to.eq(sender.address) expect(redeemEvent.args.recipient, "recipient in RedeemedMulti event").to.eq(recipient) expect(redeemEvent.args.mAssetQuantity, "mAssetQuantity in RedeemedMulti event").to.eq(inputQuantityExpectedExact) - expect(redeemEvent.args.outputs, "outputs in RedeemedMulti event").to.deep.eq(outputAssetAddresses) - expect(redeemEvent.args.outputQuantity, "outputQuantity in RedeemedMulti event").to.deep.eq(outputQuantitiesExact) + expect(redeemEvent.args.outputs, "outputs in RedeemedMulti event").to.eql(outputAssetAddresses) + expect(redeemEvent.args.outputQuantity.length, "outputQuantity length RedeemedMulti event").to.eql(outputQuantitiesExact.length) + redeemEvent.args.outputQuantity.forEach((qty, i) => { + expect(qty, `outputQuantity at index ${i} in RedeemedMulti event`).to.eq(outputQuantitiesExact[i]) + }) // Recipient should have mAsset quantity after const recipientOutputBalancesAfter = await Promise.all(outputAssets.map((b) => b.balanceOf(recipient))) @@ -336,7 +339,12 @@ describe("Feeder - Redeem", () => { expect(redeemEvent.args.recipient, "recipient in RedeemedMulti event").to.eq(recipient) expect(redeemEvent.args.mAssetQuantity, "mAssetQuantity in RedeemedMulti event").to.eq(fpTokenQuantityExact) expect(redeemEvent.args.outputs, "outputs in RedeemedMulti event").to.deep.eq(outputAssetAddresses) - expect(redeemEvent.args.outputQuantity, "outputQuantity in RedeemedMulti event").to.deep.eq(outputQuantitiesExpectedExact) + expect(redeemEvent.args.outputQuantity.length, "outputQuantity length RedeemedMulti event").to.eql( + outputQuantitiesExpectedExact.length, + ) + redeemEvent.args.outputQuantity.forEach((qty, i) => { + expect(qty, `outputQuantity at index ${i} in RedeemedMulti event`).to.eq(outputQuantitiesExpectedExact[i]) + }) expect(redeemEvent.args.scaledFee, "scaledFee in RedeemedMulti event").to.gt(0) // Recipient should have asset quantity after diff --git a/test/governance/staked-token.spec.ts b/test/governance/staked-token.spec.ts index 8e763f85..c23602f5 100644 --- a/test/governance/staked-token.spec.ts +++ b/test/governance/staked-token.spec.ts @@ -1,46 +1,29 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ /* eslint-disable @typescript-eslint/no-unused-expressions */ import { ethers } from "hardhat" import { MassetMachine, StandardAccounts } from "@utils/machines" import { MockNexus__factory } from "types/generated/factories/MockNexus__factory" -import { AssetProxy__factory, MockERC20, MockERC20__factory, MockNexus } from "types" -import { StakedToken } from "types/generated/StakedToken" -import { StakedToken__factory } from "types/generated/factories/StakedToken__factory" -import { DEAD_ADDRESS } from "index" +import { + AssetProxy__factory, + GamifiedManager__factory, + MockERC20, + MockERC20__factory, + MockNexus, + PlatformTokenVendorFactory__factory, + SignatureVerifier__factory, + StakedToken, + StakedToken__factory, +} from "types" +import { assertBNClose, DEAD_ADDRESS } from "index" import { ONE_DAY, ONE_WEEK, ZERO_ADDRESS } from "@utils/constants" import { BN, simpleToExactAmount } from "@utils/math" import { expect } from "chai" import { getTimestamp, increaseTime } from "@utils/time" -import { arrayify, hashMessage, solidityKeccak256 } from "ethers/lib/utils" +import { arrayify, solidityKeccak256 } from "ethers/lib/utils" import { BigNumberish, Signer } from "ethers" - -interface UserBalances { - raw: BN - weightedTimestamp: number - lastAction: number - permMultiplier: number - seasonMultiplier: number - timeMultiplier: number - isInCooldown: boolean -} - -interface UserStakingData { - stakedBalance: BN - votes: BN - earnedRewards: BN - stakersCooldown: BN - rewardsBalance: BN - userBalances: UserBalances -} - -enum QuestType { - PERMANENT, - SEASONAL, -} - -enum QuestStatus { - ACTIVE, - EXPIRED, -} +import { QuestStatus, QuestType, UserStakingData } from "types/stakedToken" +import { usdFormatter } from "tasks/utils" const signUserQuest = async (user: string, questId: BigNumberish, questSigner: Signer): Promise => { const messageHash = solidityKeccak256(["address", "uint256"], [user, questId]) @@ -49,7 +32,6 @@ const signUserQuest = async (user: string, questId: BigNumberish, questSigner: S } describe("Staked Token", () => { - // const ctx: Partial = {} let sa: StandardAccounts let deployTime: BN @@ -58,13 +40,24 @@ describe("Staked Token", () => { let stakedToken: StakedToken const startingMintAmount = simpleToExactAmount(10000000) + const cooldown100Percentage = simpleToExactAmount(1) + + console.log(`Staked contract size ${StakedToken__factory.bytecode.length / 2} bytes`) const redeployStakedToken = async (): Promise => { deployTime = await getTimestamp() nexus = await new MockNexus__factory(sa.default.signer).deploy(sa.governor.address, DEAD_ADDRESS, DEAD_ADDRESS) rewardToken = await new MockERC20__factory(sa.default.signer).deploy("Reward", "RWD", 18, sa.default.address, 10000000) - const stakedTokenFactory = await new StakedToken__factory(sa.default.signer) + const signatureVerifier = await new SignatureVerifier__factory(sa.default.signer).deploy() + const gamifiedManager = await new GamifiedManager__factory(sa.default.signer).deploy() + const platformTokenVendorFactory = await new PlatformTokenVendorFactory__factory(sa.default.signer).deploy() + const stakedTokenLibraryAddresses = { + "contracts/governance/staking/GamifiedManager.sol:GamifiedManager": gamifiedManager.address, + "contracts/rewards/staking/PlatformTokenVendorFactory.sol:PlatformTokenVendorFactory": platformTokenVendorFactory.address, + "contracts/governance/staking/deps/SignatureVerifier.sol:SignatureVerifier": signatureVerifier.address, + } + const stakedTokenFactory = new StakedToken__factory(stakedTokenLibraryAddresses, sa.default.signer) const stakedTokenImpl = await stakedTokenFactory.deploy( sa.questSigner.address, nexus.address, @@ -80,21 +73,22 @@ describe("Staked Token", () => { return stakedTokenFactory.attach(stakedTokenProxy.address) } - const snapshotUserStakingData = async (user = sa.default): Promise => { - const stakedBalance = await stakedToken.balanceOf(user.address) - const votes = await stakedToken.getVotes(user.address) - const earnedRewards = await stakedToken.earned(user.address) - const stakersCooldown = await stakedToken.stakersCooldowns(user.address) - const rewardsBalance = await rewardToken.balanceOf(user.address) - const userBalance = await stakedToken.balanceData(user.address) + const snapshotUserStakingData = async (user = sa.default.address): Promise => { + const stakedBalance = await stakedToken.balanceOf(user) + const votes = await stakedToken.getVotes(user) + const earnedRewards = await stakedToken.earned(user) + const [cooldownTimestamp, cooldownPercentage] = await stakedToken.stakersCooldowns(user) + const rewardsBalance = await rewardToken.balanceOf(user) + const userBalances = await stakedToken.balanceData(user) return { stakedBalance, votes, earnedRewards, - stakersCooldown, + cooldownTimestamp, + cooldownPercentage, rewardsBalance, - userBalances: userBalance, + userBalances, } } @@ -104,8 +98,6 @@ describe("Staked Token", () => { sa = mAssetMachine.sa }) - // shouldBehaveLikeModule(ctx as Required) - context("deploy and initialize", () => { before(async () => { stakedToken = await redeployStakedToken() @@ -119,6 +111,7 @@ describe("Staked Token", () => { expect(await stakedToken.STAKED_TOKEN(), "staked token").to.eq(rewardToken.address) expect(await stakedToken.COOLDOWN_SECONDS(), "cooldown").to.eq(ONE_WEEK) expect(await stakedToken.UNSTAKE_WINDOW(), "unstake window").to.eq(ONE_DAY.mul(2)) + expect(await stakedToken.COOLDOWN_PERCENTAGE_SCALE(), "unstake window").to.eq(cooldown100Percentage) // eslint-disable-next-line no-underscore-dangle expect(await stakedToken._signer(), "quest signer").to.eq(sa.questSigner.address) @@ -130,50 +123,91 @@ describe("Staked Token", () => { stakedToken = await redeployStakedToken() await rewardToken.connect(sa.default.signer).approve(stakedToken.address, stakedAmount.mul(3)) - const beforeDefaultData = await snapshotUserStakingData(sa.default) - expect(beforeDefaultData.stakedBalance, "staker stkRWD before").to.eq(0) - expect(beforeDefaultData.rewardsBalance, "staker RWD before").to.eq(startingMintAmount) - expect(beforeDefaultData.votes, "staker votes before").to.eq(0) - expect(beforeDefaultData.stakersCooldown, "staker cooldown before").to.eq(0) - - const beforeDelegateData = await snapshotUserStakingData(sa.dummy1) - expect(beforeDelegateData.stakedBalance, "delegate stkRWD before").to.eq(0) - expect(beforeDelegateData.rewardsBalance, "delegate RWD before").to.eq(0) - expect(beforeDelegateData.votes, "delegate votes before").to.eq(0) - expect(beforeDelegateData.stakersCooldown, "delegate cooldown before").to.eq(0) + const stakerDataBefore = await snapshotUserStakingData(sa.default.address) + expect(stakerDataBefore.userBalances.weightedTimestamp, "weighted timestamp before").to.eq(0) + expect(stakerDataBefore.userBalances.lastAction, "last action before").to.eq(0) + expect(stakerDataBefore.userBalances.permMultiplier, "perm multiplier before").to.eq(0) + expect(stakerDataBefore.userBalances.seasonMultiplier, "season multiplier before").to.eq(0) + expect(stakerDataBefore.userBalances.timeMultiplier, "time multiplier before").to.eq(0) + expect(stakerDataBefore.userBalances.cooldownMultiplier, "cooldown multiplier before").to.eq(0) + expect(stakerDataBefore.stakedBalance, "staker stkRWD before").to.eq(0) + expect(stakerDataBefore.rewardsBalance, "staker RWD before").to.eq(startingMintAmount) + expect(stakerDataBefore.votes, "staker votes before").to.eq(0) + expect(stakerDataBefore.cooldownTimestamp, "staker cooldown before").to.eq(0) + + const delegateDataBefore = await snapshotUserStakingData(sa.dummy1.address) + expect(delegateDataBefore.stakedBalance, "delegate stkRWD before").to.eq(0) + expect(delegateDataBefore.rewardsBalance, "delegate RWD before").to.eq(0) + expect(delegateDataBefore.votes, "delegate votes before").to.eq(0) + expect(delegateDataBefore.cooldownTimestamp, "delegate cooldown before").to.eq(0) expect(await stakedToken.totalSupply(), "total staked before").to.eq(0) }) it("should delegate to self by default", async () => { const tx = await stakedToken["stake(uint256)"](stakedAmount) - await expect(tx).to.emit(stakedToken, "Staked").withArgs(sa.default.address, stakedAmount, ZERO_ADDRESS) - await expect(tx).to.emit(stakedToken, "DelegateChanged").not - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.default.address, 0, stakedAmount) - await expect(tx).to.emit(rewardToken, "Transfer").withArgs(sa.default.address, stakedToken.address, stakedAmount) - const afterData = await snapshotUserStakingData(sa.default) - expect(afterData.stakedBalance, "staker stkRWD after").to.eq(stakedAmount) + const stakedTimestamp = await getTimestamp() + + await expect(tx) + .to.emit(stakedToken, "Staked") + .withArgs(sa.default.address, stakedAmount, ZERO_ADDRESS) + await expect(tx).to.emit(stakedToken, "DelegateChanged").not + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.default.address, 0, stakedAmount) + await expect(tx) + .to.emit(rewardToken, "Transfer") + .withArgs(sa.default.address, stakedToken.address, stakedAmount) + + const afterData = await snapshotUserStakingData(sa.default.address) + + expect(afterData.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(afterData.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(afterData.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(afterData.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(afterData.userBalances.lastAction, "last action after").to.eq(stakedTimestamp) + expect(afterData.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(afterData.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(afterData.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(afterData.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(afterData.stakedBalance, "staked balance after").to.eq(stakedAmount) expect(afterData.votes, "staker votes after").to.eq(stakedAmount) - expect(afterData.stakersCooldown, "staker cooldown after").to.eq(0) expect(await stakedToken.totalSupply(), "total staked after").to.eq(stakedAmount) }) it("should assign delegate", async () => { const tx = await stakedToken["stake(uint256,address)"](stakedAmount, sa.dummy1.address) - await expect(tx).to.emit(stakedToken, "Staked").withArgs(sa.default.address, stakedAmount, sa.dummy1.address) - await expect(tx).to.emit(stakedToken, "DelegateChanged").withArgs(sa.default.address, sa.default.address, sa.dummy1.address) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.dummy1.address, 0, stakedAmount) - await expect(tx).to.emit(rewardToken, "Transfer").withArgs(sa.default.address, stakedToken.address, stakedAmount) - const afterStakerData = await snapshotUserStakingData(sa.default) - expect(afterStakerData.stakedBalance, "staker stkRWD after").to.eq(stakedAmount) - expect(afterStakerData.votes, "staker votes after").to.eq(0) - expect(afterStakerData.stakersCooldown, "staker cooldown after").to.eq(0) - - const afterDelegateData = await snapshotUserStakingData(sa.dummy1) - expect(afterDelegateData.stakedBalance, "delegate stkRWD after").to.eq(0) - expect(afterDelegateData.votes, "delegate votes after").to.eq(stakedAmount) - expect(afterDelegateData.stakersCooldown, "delegate cooldown after").to.eq(0) + const stakedTimestamp = await getTimestamp() + + await expect(tx) + .to.emit(stakedToken, "Staked") + .withArgs(sa.default.address, stakedAmount, sa.dummy1.address) + await expect(tx) + .to.emit(stakedToken, "DelegateChanged") + .withArgs(sa.default.address, sa.default.address, sa.dummy1.address) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.dummy1.address, 0, stakedAmount) + await expect(tx) + .to.emit(rewardToken, "Transfer") + .withArgs(sa.default.address, stakedToken.address, stakedAmount) + + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.userBalances.raw, "staker raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "staker weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "staker last action after").to.eq(stakedTimestamp) + expect(stakerDataAfter.stakedBalance, "staker stkRWD after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staker votes after").to.eq(0) + expect(stakerDataAfter.cooldownTimestamp, "staker cooldown after").to.eq(0) + + const delegateDataAfter = await snapshotUserStakingData(sa.dummy1.address) + expect(delegateDataAfter.userBalances.raw, "delegate raw balance after").to.eq(0) + expect(delegateDataAfter.userBalances.weightedTimestamp, "delegate weighted timestamp after").to.eq(0) + expect(delegateDataAfter.userBalances.lastAction, "delegate last action after").to.eq(0) + expect(delegateDataAfter.stakedBalance, "delegate stkRWD after").to.eq(0) + expect(delegateDataAfter.votes, "delegate votes after").to.eq(stakedAmount) + expect(delegateDataAfter.cooldownTimestamp, "delegate cooldown after").to.eq(0) expect(await stakedToken.totalSupply(), "total staked after").to.eq(stakedAmount) }) @@ -185,15 +219,15 @@ describe("Staked Token", () => { await stakedToken["stake(uint256,address)"](stakedAmount, sa.dummy1.address) await stakedToken.connect(sa.dummy1.signer)["stake(uint256,address)"](delegateStakedAmount, sa.dummy2.address) - const afterStakerData = await snapshotUserStakingData(sa.default) + const afterStakerData = await snapshotUserStakingData(sa.default.address) expect(afterStakerData.stakedBalance, "staker stkRWD after").to.eq(stakedAmount) expect(afterStakerData.votes, "staker votes after").to.eq(0) - const afterDelegateData = await snapshotUserStakingData(sa.dummy1) + const afterDelegateData = await snapshotUserStakingData(sa.dummy1.address) expect(afterDelegateData.stakedBalance, "delegate stkRWD after").to.eq(delegateStakedAmount) expect(afterDelegateData.votes, "delegate votes after").to.eq(stakedAmount) - const afterDelegatesDelegateData = await snapshotUserStakingData(sa.dummy2) + const afterDelegatesDelegateData = await snapshotUserStakingData(sa.dummy2.address) expect(afterDelegatesDelegateData.stakedBalance, "delegate stkRWD after").to.eq(0) expect(afterDelegatesDelegateData.votes, "delegate votes after").to.eq(delegateStakedAmount) @@ -209,74 +243,116 @@ describe("Staked Token", () => { it("should change by staker from self to delegate", async () => { await stakedToken["stake(uint256)"](stakedAmount) - const stakerDataBefore = await snapshotUserStakingData(sa.default) + const stakedTimestamp = await getTimestamp() + const stakerDataBefore = await snapshotUserStakingData(sa.default.address) expect(stakerDataBefore.votes).to.equal(stakedAmount) expect(stakerDataBefore.stakedBalance).to.equal(stakedAmount) - const delegateDataBefore = await snapshotUserStakingData(sa.dummy1) + const delegateDataBefore = await snapshotUserStakingData(sa.dummy1.address) expect(delegateDataBefore.votes).to.equal(0) + await increaseTime(ONE_WEEK) + + // Staker delegates to delegate const tx = await stakedToken.delegate(sa.dummy1.address) - await expect(tx).to.emit(stakedToken, "DelegateChanged").withArgs(sa.default.address, sa.default.address, sa.dummy1.address) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.default.address, stakedAmount, 0) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.dummy1.address, 0, stakedAmount) - const stakerDataAfter = await snapshotUserStakingData(sa.default) - expect(stakerDataAfter.votes).to.equal(0) - expect(stakerDataAfter.stakedBalance).to.equal(stakedAmount) - const delegateDataAfter = await snapshotUserStakingData(sa.dummy1) - expect(delegateDataAfter.votes).to.equal(stakedAmount) - expect(delegateDataAfter.stakedBalance).to.equal(0) + // Events from delegate tx + await expect(tx) + .to.emit(stakedToken, "DelegateChanged") + .withArgs(sa.default.address, sa.default.address, sa.dummy1.address) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.default.address, stakedAmount, 0) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.dummy1.address, 0, stakedAmount) + + // Staker + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.userBalances.raw, "staker raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "staker weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "staker last action after").to.eq(stakedTimestamp) + expect(stakerDataAfter.votes, "staker votes after").to.equal(0) + expect(stakerDataAfter.stakedBalance, "staker staked balance after").to.equal(stakedAmount) + // Delegate + const delegateDataAfter = await snapshotUserStakingData(sa.dummy1.address) + expect(delegateDataAfter.userBalances.raw, "delegate raw balance after").to.eq(0) + expect(delegateDataAfter.userBalances.weightedTimestamp, "delegate weighted timestamp after").to.eq(0) + expect(delegateDataAfter.userBalances.lastAction, "delegate last action after").to.eq(0) + expect(delegateDataAfter.votes, "delegate votes after").to.equal(stakedAmount) + expect(delegateDataAfter.stakedBalance, "delegate staked balance after").to.equal(0) }) - it("should change by staker from 1 to 2", async () => { + it("should change delegate by staker from dummy 1 to 2", async () => { await stakedToken["stake(uint256,address)"](stakedAmount, sa.dummy1.address) - const stakerDataBefore = await snapshotUserStakingData(sa.default) + const stakerDataBefore = await snapshotUserStakingData(sa.default.address) expect(stakerDataBefore.votes).to.equal(0) - const oldDelegateDataBefore = await snapshotUserStakingData(sa.dummy1) + const oldDelegateDataBefore = await snapshotUserStakingData(sa.dummy1.address) expect(oldDelegateDataBefore.votes).to.equal(stakedAmount) - const newDelegateDataBefore = await snapshotUserStakingData(sa.dummy2) + const newDelegateDataBefore = await snapshotUserStakingData(sa.dummy2.address) expect(newDelegateDataBefore.votes).to.equal(0) const tx = await stakedToken.delegate(sa.dummy2.address) - await expect(tx).to.emit(stakedToken, "DelegateChanged").withArgs(sa.default.address, sa.dummy1.address, sa.dummy2.address) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.dummy1.address, stakedAmount, 0) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.dummy2.address, 0, stakedAmount) - - const stakerDataAfter = await snapshotUserStakingData(sa.default) + await expect(tx) + .to.emit(stakedToken, "DelegateChanged") + .withArgs(sa.default.address, sa.dummy1.address, sa.dummy2.address) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.dummy1.address, stakedAmount, 0) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.dummy2.address, 0, stakedAmount) + + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) expect(stakerDataAfter.votes).to.equal(0) expect(stakerDataAfter.stakedBalance).to.equal(stakedAmount) - const oldDelegateDataAfter = await snapshotUserStakingData(sa.dummy1) + const oldDelegateDataAfter = await snapshotUserStakingData(sa.dummy1.address) expect(oldDelegateDataAfter.votes).to.equal(0) expect(oldDelegateDataAfter.stakedBalance).to.equal(0) - const newDelegateDataAfter = await snapshotUserStakingData(sa.dummy2) + const newDelegateDataAfter = await snapshotUserStakingData(sa.dummy2.address) expect(newDelegateDataAfter.votes).to.equal(stakedAmount) expect(newDelegateDataAfter.stakedBalance).to.equal(0) }) it("should change by staker from delegate to self", async () => { await stakedToken["stake(uint256,address)"](stakedAmount, sa.dummy1.address) - const stakerDataBefore = await snapshotUserStakingData(sa.default) + const stakedTimestamp = await getTimestamp() + const stakerDataBefore = await snapshotUserStakingData(sa.default.address) expect(stakerDataBefore.votes).to.equal(0) expect(stakerDataBefore.stakedBalance).to.equal(stakedAmount) - const delegateDataBefore = await snapshotUserStakingData(sa.dummy1) + const delegateDataBefore = await snapshotUserStakingData(sa.dummy1.address) expect(delegateDataBefore.votes).to.equal(stakedAmount) + // Staker delegates from delegate back to themselves const tx = await stakedToken.delegate(sa.default.address) - await expect(tx).to.emit(stakedToken, "DelegateChanged").withArgs(sa.default.address, sa.dummy1.address, sa.default.address) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.default.address, 0, stakedAmount) - await expect(tx).to.emit(stakedToken, "DelegateVotesChanged").withArgs(sa.dummy1.address, stakedAmount, 0) - const stakerDataAfter = await snapshotUserStakingData(sa.default) - expect(stakerDataAfter.votes).to.equal(stakedAmount) - expect(stakerDataAfter.stakedBalance).to.equal(stakedAmount) - expect(stakerDataAfter.stakedBalance).to.equal(stakedAmount) - const delegateDataAfter = await snapshotUserStakingData(sa.dummy1) - expect(delegateDataAfter.votes).to.equal(0) - expect(delegateDataAfter.stakedBalance).to.equal(0) + // Events + await expect(tx) + .to.emit(stakedToken, "DelegateChanged") + .withArgs(sa.default.address, sa.dummy1.address, sa.default.address) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.default.address, 0, stakedAmount) + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.dummy1.address, stakedAmount, 0) + + // Staker + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.userBalances.raw, "staker raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "staker weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "staker last action after").to.eq(stakedTimestamp) + expect(stakerDataAfter.votes, "staker votes after").to.equal(stakedAmount) + expect(stakerDataAfter.stakedBalance, "staker staked balance after").to.equal(stakedAmount) + // Delegate + const delegateDataAfter = await snapshotUserStakingData(sa.dummy1.address) + expect(delegateDataAfter.votes, "delegate votes after").to.equal(0) + expect(delegateDataAfter.stakedBalance, "delegate staked balance after").to.equal(0) }) it("by delegate", async () => { const tx = await stakedToken.connect(sa.dummy1.signer).delegate(sa.dummy2.address) - await expect(tx).to.emit(stakedToken, "DelegateChanged").withArgs(sa.dummy1.address, sa.dummy1.address, sa.dummy2.address) + await expect(tx) + .to.emit(stakedToken, "DelegateChanged") + .withArgs(sa.dummy1.address, sa.dummy1.address, sa.dummy2.address) }) context("should fail", () => { it("by delegate", async () => { @@ -317,7 +393,7 @@ describe("Staked Token", () => { }) it("should allow governor to add a permanent quest", async () => { id += 1 - const multiplier = 60 // 1.6x + const multiplier = 40 // 1.4x const expiry = deployTime.add(ONE_WEEK.mul(26)) const tx = await stakedToken.connect(sa.governor.signer).addQuest(QuestType.PERMANENT, multiplier, expiry) @@ -335,8 +411,8 @@ describe("Staked Token", () => { it("quest with 1.01x multiplier", async () => { await stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 1, deployTime.add(ONE_WEEK.mul(12))) }) - it("quest with 2x multiplier", async () => { - await stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 100, deployTime.add(ONE_WEEK.mul(12))) + it("quest with 50x multiplier", async () => { + await stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 50, deployTime.add(ONE_WEEK.mul(12))) }) it("quest with 1 day expiry", async () => { const currentTime = await getTimestamp() @@ -358,12 +434,12 @@ describe("Staked Token", () => { it("with 0 multiplier", async () => { await expect( stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 0, deployTime.add(ONE_WEEK)), - ).to.revertedWith("Quest multiplier too large > 2x") + ).to.revertedWith("Quest multiplier too large > 1.5x") }) - it("with > 2x multiplier", async () => { + it("with > 1.5x multiplier", async () => { await expect( - stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 101, deployTime.add(ONE_WEEK)), - ).to.revertedWith("Quest multiplier too large > 2x") + stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 51, deployTime.add(ONE_WEEK)), + ).to.revertedWith("Quest multiplier too large > 1.5x") }) }) }) @@ -379,7 +455,9 @@ describe("Staked Token", () => { const currentTime = await getTimestamp() const tx = await stakedToken.connect(sa.governor.signer).expireQuest(id) - await expect(tx).to.emit(stakedToken, "QuestExpired").withArgs(id) + await expect(tx) + .to.emit(stakedToken, "QuestExpired") + .withArgs(id) const quest = await stakedToken.getQuest(id) expect(quest.status).to.eq(QuestStatus.EXPIRED) @@ -393,7 +471,9 @@ describe("Staked Token", () => { const currentTime = await getTimestamp() const tx = await stakedToken.connect(sa.governor.signer).expireQuest(id) - await expect(tx).to.emit(stakedToken, "QuestExpired").withArgs(id) + await expect(tx) + .to.emit(stakedToken, "QuestExpired") + .withArgs(id) const quest = await stakedToken.getQuest(id) expect(quest.status).to.eq(QuestStatus.EXPIRED) @@ -425,11 +505,14 @@ describe("Staked Token", () => { const expiry = deployTime.add(ONE_WEEK.mul(12)) await stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 10, expiry) await stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, 10, expiry) + expect(await stakedToken.seasonEpoch(), "season epoch before").to.gt(deployTime) }) it("should allow governor to start season after 39 weeks", async () => { await increaseTime(ONE_WEEK.mul(39).add(60)) const tx = await stakedToken.connect(sa.governor.signer).startNewQuestSeason() await expect(tx).to.emit(stakedToken, "QuestSeasonEnded") + const currentTime = await getTimestamp() + expect(await stakedToken.seasonEpoch(), "season epoch after").to.eq(currentTime) }) context("should fail to start season", () => { it("from deployer", async () => { @@ -441,111 +524,397 @@ describe("Staked Token", () => { }) }) }) - context("complete quest", () => { - let currentTime + context("complete quests", () => { + let stakedTime let permanentQuestId: BN let seasonQuestId: BN const permanentMultiplier = 10 const seasonMultiplier = 20 - before(async () => { - currentTime = await getTimestamp() - const expiry = currentTime.add(ONE_WEEK.mul(12)) + beforeEach(async () => { + stakedToken = await redeployStakedToken() + await rewardToken.connect(sa.default.signer).approve(stakedToken.address, simpleToExactAmount(10000)) + await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) + + stakedTime = await getTimestamp() + const expiry = stakedTime.add(ONE_WEEK.mul(12)) await stakedToken.connect(sa.governor.signer).addQuest(QuestType.PERMANENT, permanentMultiplier, expiry) const tx = await stakedToken.connect(sa.governor.signer).addQuest(QuestType.SEASONAL, seasonMultiplier, expiry) const receipt = await tx.wait() seasonQuestId = receipt.events[0].args.id permanentQuestId = seasonQuestId.sub(1) }) - it("should have quest signer set", async () => { - // eslint-disable-next-line no-underscore-dangle - expect(await stakedToken._signer(), "quest signer").to.eq(sa.questSigner.address) - }) - it("should get message hash", async () => { - const messageHash = solidityKeccak256(["address", "uint256"], [sa.default.address, seasonQuestId]) - expect(await stakedToken.getMessageHash(sa.default.address, seasonQuestId), "account and id hash").to.eq(messageHash) - }) - it("should get ETH signed message hash", async () => { - const messageHash = solidityKeccak256(["address", "uint256"], [sa.default.address, seasonQuestId]) - const ethMessageHash = solidityKeccak256(["string", "bytes32"], ["\x19Ethereum Signed Message:\n32", messageHash]) - expect(hashMessage(arrayify(messageHash)), "ethers hashMessage").to.eq(ethMessageHash) - expect(await stakedToken.getEthSignedMessageHash(messageHash), "contract message hash").to.eq(ethMessageHash) - }) - it("should recover quest signer's signature", async () => { - const messageHash = solidityKeccak256(["address", "uint256"], [sa.default.address, seasonQuestId]) - const ethMessageHash = solidityKeccak256(["string", "bytes32"], ["\x19Ethereum Signed Message:\n32", messageHash]) - // Ethers signMessage will prefix the "\x19Ethereum signed message:\n32" - const signature = await sa.questSigner.signer.signMessage(arrayify(messageHash)) - expect(await stakedToken.recoverSigner(arrayify(ethMessageHash), signature), "recovered quest signer address").to.eq( - sa.questSigner.address, - ) - }) - it("verify quest signer signature", async () => { - const signature = await signUserQuest(sa.default.address, seasonQuestId, sa.questSigner.signer) - expect(await stakedToken.verify(sa.default.address, seasonQuestId, signature), "verify quest signer").to.be.true - }) it("should allow quest signer to complete a user's seasonal quest", async () => { const userAddress = sa.default.address - const userDataBefore = await snapshotUserStakingData(sa.default) expect(await stakedToken.hasCompleted(userAddress, seasonQuestId), "quest completed before").to.be.false // Complete User Season Quest const signature = await signUserQuest(userAddress, seasonQuestId, sa.questSigner.signer) - const tx = await stakedToken.connect(sa.default.signer).completeQuest(userAddress, seasonQuestId, signature) + const tx = await stakedToken.connect(sa.default.signer).completeQuests(userAddress, [seasonQuestId], [signature]) + + const completeQuestTimestamp = await getTimestamp() + + // Check events + await expect(tx) + .to.emit(stakedToken, "QuestComplete") + .withArgs(userAddress, seasonQuestId) - await expect(tx).to.emit(stakedToken, "QuestComplete").withArgs(userAddress, seasonQuestId) + // Check data expect(await stakedToken.hasCompleted(userAddress, seasonQuestId), "quest completed after").to.be.true - const userDataAfter = await snapshotUserStakingData(sa.default) - expect(userDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq( - userDataBefore.userBalances.seasonMultiplier + seasonMultiplier, - ) - expect(userDataAfter.userBalances.permMultiplier, "permanent multiplier after").to.eq( - userDataBefore.userBalances.permMultiplier, - ) - // expect(userDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(userDataBefore.userBalances.timeMultiplier) - // TODO check weighted timestamp - // TODO check votes + const userDataAfter = await snapshotUserStakingData(userAddress) + expect(userDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(userDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(userDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(userDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTime) + expect(userDataAfter.userBalances.lastAction, "last action after").to.eq(completeQuestTimestamp) + expect(userDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(userDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(seasonMultiplier) + expect(userDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(userDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + const expectedBalance = stakedAmount.mul(100 + seasonMultiplier).div(100) + expect(userDataAfter.stakedBalance, "staked balance after").to.eq(expectedBalance) + expect(userDataAfter.votes, "votes after").to.eq(expectedBalance) }) it("should allow quest signer to complete a user's permanent quest", async () => { - const userAddress = sa.dummy1.address - const userDataBefore = await snapshotUserStakingData(sa.dummy1) + const userAddress = sa.default.address expect(await stakedToken.hasCompleted(userAddress, permanentQuestId), "quest completed before").to.be.false // Complete User Permanent Quest const signature = await signUserQuest(userAddress, permanentQuestId, sa.questSigner.signer) - const tx = await stakedToken.connect(sa.questSigner.signer).completeQuest(userAddress, permanentQuestId, signature) + const tx = await stakedToken.connect(sa.questSigner.signer).completeQuests(userAddress, [permanentQuestId], [signature]) - await expect(tx).to.emit(stakedToken, "QuestComplete").withArgs(userAddress, permanentQuestId) + const completeQuestTimestamp = await getTimestamp() + + // Check events + await expect(tx) + .to.emit(stakedToken, "QuestComplete") + .withArgs(userAddress, permanentQuestId) + + // Check data expect(await stakedToken.hasCompleted(userAddress, permanentQuestId), "quest completed after").to.be.true - const userDataAfter = await snapshotUserStakingData(sa.dummy1) - expect(userDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq( - userDataBefore.userBalances.seasonMultiplier, - ) - expect(userDataAfter.userBalances.permMultiplier, "permanent multiplier after").to.eq( - userDataBefore.userBalances.permMultiplier + permanentMultiplier, - ) - expect(userDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(userDataBefore.userBalances.timeMultiplier) - // TODO check weighted timestamp - // TODO check votes + const userDataAfter = await snapshotUserStakingData(userAddress) + expect(userDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(userDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(userDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(userDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTime) + expect(userDataAfter.userBalances.lastAction, "last action after").to.eq(completeQuestTimestamp) + expect(userDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(permanentMultiplier) + expect(userDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(userDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(userDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + const expectedBalance = stakedAmount.mul(100 + permanentMultiplier).div(100) + expect(userDataAfter.stakedBalance, "staked balance after").to.eq(expectedBalance) + expect(userDataAfter.votes, "votes after").to.eq(expectedBalance) + }) + it("should complete user quest before a user stakes", async () => { + const userAddress = sa.dummy1.address + expect(await stakedToken.hasCompleted(userAddress, permanentQuestId), "quest completed before").to.be.false + + // Complete User Permanent and Seasonal Quests + const permSignature = await signUserQuest(userAddress, permanentQuestId, sa.questSigner.signer) + const seasonSignature = await signUserQuest(userAddress, seasonQuestId, sa.questSigner.signer) + const tx = await stakedToken + .connect(sa.questSigner.signer) + .completeQuests(userAddress, [permanentQuestId, seasonQuestId], [permSignature, seasonSignature]) + + const completeQuestTimestamp = await getTimestamp() + + // Check events + await expect(tx) + .to.emit(stakedToken, "QuestComplete") + .withArgs(userAddress, permanentQuestId) + + // Check data + expect(await stakedToken.hasCompleted(userAddress, permanentQuestId), "quest completed after").to.be.true + const userDataAfter = await snapshotUserStakingData(userAddress) + expect(userDataAfter.userBalances.raw, "staked raw balance after").to.eq(0) + expect(userDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(0) + expect(userDataAfter.userBalances.lastAction, "last action after").to.eq(completeQuestTimestamp) + expect(userDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(permanentMultiplier) + expect(userDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(seasonMultiplier) + expect(userDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(userDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(userDataAfter.stakedBalance, "staked balance after").to.eq(0) + expect(userDataAfter.votes, "votes after").to.eq(0) }) it("should fail to complete a user quest again", async () => { const userAddress = sa.dummy2.address const signature = await signUserQuest(userAddress, permanentQuestId, sa.questSigner.signer) - await stakedToken.connect(sa.questSigner.signer).completeQuest(userAddress, permanentQuestId, signature) + await stakedToken.connect(sa.questSigner.signer).completeQuests(userAddress, [permanentQuestId], [signature]) await expect( - stakedToken.connect(sa.questSigner.signer).completeQuest(userAddress, permanentQuestId, signature), + stakedToken.connect(sa.questSigner.signer).completeQuests(userAddress, [permanentQuestId], [signature]), ).to.revertedWith("Err: Already Completed") }) it("should fail a user signing quest completion", async () => { const userAddress = sa.dummy3.address const signature = await signUserQuest(userAddress, permanentQuestId, sa.dummy3.signer) - await expect(stakedToken.connect(sa.dummy3.signer).completeQuest(userAddress, permanentQuestId, signature)).to.revertedWith( - "Err: Invalid Signature", - ) + await expect( + stakedToken.connect(sa.dummy3.signer).completeQuests(userAddress, [permanentQuestId], [signature]), + ).to.revertedWith("Err: Invalid Signature") + }) + }) + context("time multiplier", () => { + let stakerDataBefore: UserStakingData + let anySigner: Signer + beforeEach(async () => { + stakedToken = await redeployStakedToken() + await rewardToken.connect(sa.default.signer).approve(stakedToken.address, simpleToExactAmount(10000)) + await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) + + anySigner = sa.dummy4.signer + }) + it("staker data just after stake", async () => { + stakerDataBefore = await snapshotUserStakingData(sa.default.address) + expect(stakerDataBefore.userBalances.timeMultiplier).to.eq(0) + expect(stakerDataBefore.userBalances.raw).to.eq(stakedAmount) + expect(stakerDataBefore.votes).to.eq(stakedAmount) + expect(stakerDataBefore.stakedBalance).to.eq(stakedAmount) + }) + const runs = [ + { weeks: 13, multiplierBefore: BN.from(0), multiplierAfter: BN.from(20) }, + { weeks: 26, multiplierBefore: BN.from(20), multiplierAfter: BN.from(30) }, + { weeks: 52, multiplierBefore: BN.from(30), multiplierAfter: BN.from(40) }, + { weeks: 78, multiplierBefore: BN.from(40), multiplierAfter: BN.from(50) }, + { weeks: 104, multiplierBefore: BN.from(50), multiplierAfter: BN.from(60) }, + { weeks: 312, multiplierBefore: BN.from(60), multiplierAfter: BN.from(60) }, + ] + runs.forEach((run) => { + it(`anyone can review timestamp before ${run.weeks} weeks`, async () => { + await increaseTime(ONE_WEEK.mul(run.weeks).sub(60)) + + if (run.multiplierBefore.eq(0)) { + await expect(stakedToken.connect(anySigner).reviewTimestamp(sa.default.address)).to.revertedWith( + "Nothing worth poking here", + ) + } else { + await stakedToken.connect(anySigner).reviewTimestamp(sa.default.address) + } + + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.userBalances.timeMultiplier, "timeMultiplier After").to.eq(run.multiplierBefore) + expect(stakerDataAfter.userBalances.raw, "raw balance after").to.eq(stakedAmount) + // balance = staked amount * (100 + time multiplier) / 100 + const expectedBalance = stakedAmount.mul(run.multiplierBefore.add(100)).div(100) + expect(stakerDataAfter.votes, "votes after").to.eq(expectedBalance) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(expectedBalance) + }) + it(`anyone can review timestamp after ${run.weeks} weeks`, async () => { + await increaseTime(ONE_WEEK.mul(run.weeks).add(60)) + + await stakedToken.connect(anySigner).reviewTimestamp(sa.default.address) + + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.userBalances.timeMultiplier, "timeMultiplier After").to.eq(run.multiplierAfter) + expect(stakerDataAfter.userBalances.raw, "raw balance after").to.eq(stakedAmount) + // balance = staked amount * (100 + time multiplier) / 100 + const expectedBalance = stakedAmount.mul(run.multiplierAfter.add(100)).div(100) + expect(stakerDataAfter.votes, "votes after").to.eq(expectedBalance) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(expectedBalance) + }) + }) + }) + context("multiple multipliers", () => { + const quests: { type: QuestType; multiplier: number; weeks: number }[] = [ + { type: QuestType.PERMANENT, multiplier: 12, weeks: 12 }, + { type: QuestType.PERMANENT, multiplier: 22, weeks: 4 }, + { type: QuestType.SEASONAL, multiplier: 5, weeks: 6 }, + { type: QuestType.SEASONAL, multiplier: 8, weeks: 10 }, + ] + beforeEach(async () => { + stakedToken = await redeployStakedToken() + + const questStart = await getTimestamp() + for (const quest of quests) { + await stakedToken + .connect(sa.governor.signer) + .addQuest(quest.type, quest.multiplier, questStart.add(ONE_WEEK.mul(quest.weeks))) + } + + await rewardToken.connect(sa.default.signer).approve(stakedToken.address, simpleToExactAmount(10000)) + await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) + }) + const runs: { + desc: string + weeks: number + completedQuests: number[] + cooldown?: { + start: number + end?: number + percentage: BN + } + timeMultiplier?: number + permMultiplier?: number + seasonMultiplier?: number + cooldownMultiplier?: number + reviewTimestamp?: boolean + }[] = [ + { desc: "no multipliers", weeks: 1, completedQuests: [] }, + { + desc: "all quests before 13 weeks", + weeks: 2, + completedQuests: [0, 1, 2, 3], + timeMultiplier: 0, + permMultiplier: 34, + seasonMultiplier: 13, + }, + { + desc: "all quests after 13 weeks", + weeks: 14, + completedQuests: [0, 1, 2, 3], + timeMultiplier: 20, + permMultiplier: 34, + seasonMultiplier: 13, + reviewTimestamp: true, + }, + { + desc: "only perm quests after 27 weeks", + weeks: 27, + completedQuests: [0, 1], + timeMultiplier: 30, + permMultiplier: 34, + seasonMultiplier: 0, + reviewTimestamp: true, + }, + { + desc: "only season quests after 55 weeks", + weeks: 55, + completedQuests: [2, 3], + timeMultiplier: 40, + permMultiplier: 0, + seasonMultiplier: 13, + reviewTimestamp: true, + }, + { + desc: "no quests, 10 weeks in 100% cooldown", + weeks: 10, + completedQuests: [], + cooldown: { + start: 8, + percentage: simpleToExactAmount(1), + }, + timeMultiplier: 0, + permMultiplier: 0, + seasonMultiplier: 0, + cooldownMultiplier: 100, + }, + { + desc: "no quests, 11 weeks out of 100% cooldown not ended", + weeks: 11, + completedQuests: [], + cooldown: { + start: 8, + percentage: simpleToExactAmount(1), + }, + timeMultiplier: 0, + permMultiplier: 0, + seasonMultiplier: 0, + cooldownMultiplier: 100, + }, + { + desc: "no quests, 11 weeks out of 100% cooldown ended", + weeks: 11, + completedQuests: [], + cooldown: { + start: 8, + percentage: simpleToExactAmount(1), + end: 10, + }, + timeMultiplier: 0, + permMultiplier: 0, + seasonMultiplier: 0, + cooldownMultiplier: 0, + }, + { + desc: "all quests, 20 weeks in 20% cooldown", + weeks: 20, + completedQuests: [0, 1, 2, 3], + cooldown: { + start: 19, + percentage: simpleToExactAmount(0.2), + }, + timeMultiplier: 20, + permMultiplier: 34, + seasonMultiplier: 13, + cooldownMultiplier: 20, + }, + { + desc: "all quests, 23 weeks after 30% cooldown not ended", + weeks: 23, + completedQuests: [0, 1, 2, 3], + cooldown: { + start: 19, + percentage: simpleToExactAmount(0.3), + }, + timeMultiplier: 20, + permMultiplier: 34, + seasonMultiplier: 13, + cooldownMultiplier: 30, + }, + { + desc: "all quests, 24 weeks after 20% cooldown ended", + weeks: 24, + completedQuests: [0, 1, 2, 3], + cooldown: { + start: 19, + end: 23, + percentage: simpleToExactAmount(0.2), + }, + timeMultiplier: 20, + permMultiplier: 34, + seasonMultiplier: 13, + cooldownMultiplier: 0, + }, + ] + runs.forEach((run) => { + it(run.desc, async () => { + const user = sa.default.address + if (run.completedQuests.length) { + const signatures = [] + for (const questId of run.completedQuests) { + signatures.push(await signUserQuest(user, questId, sa.questSigner.signer)) + } + await stakedToken.completeQuests(user, run.completedQuests, signatures) + } + + if (run.cooldown?.start) { + await increaseTime(ONE_WEEK.mul(run.cooldown.start)) + await stakedToken.startCooldown(run.cooldown.percentage) + + if (run.cooldown.end) { + await increaseTime(ONE_WEEK.mul(run.weeks - run.cooldown.end)) + await stakedToken.endCooldown() + await increaseTime(ONE_WEEK.mul(run.weeks - run.cooldown.end)) + } else { + await increaseTime(ONE_WEEK.mul(run.weeks - run.cooldown.start)) + } + } else { + await increaseTime(ONE_WEEK.mul(run.weeks)) + } + + if (run.reviewTimestamp) { + await stakedToken.reviewTimestamp(user) + } + + const timeMultiplierExpected = BN.from(run.timeMultiplier || 0) + const permMultiplierExpected = BN.from(run.permMultiplier || 0) + const seasonMultiplierExpected = BN.from(run.seasonMultiplier || 0) + const cooldownMultiplierExpected = BN.from(run.cooldownMultiplier || 0) + + const questBalanceExpected = stakedAmount.mul(permMultiplierExpected.add(seasonMultiplierExpected).add(100)).div(100) + const timeBalanceExpected = questBalanceExpected.mul(timeMultiplierExpected.add(100)).div(100) + const balanceExpected = timeBalanceExpected.mul(BN.from(100).sub(cooldownMultiplierExpected)).div(100) + + const stakerDataAfter = await snapshotUserStakingData(user) + expect(stakerDataAfter.userBalances.timeMultiplier, "timeMultiplier After").to.eq(timeMultiplierExpected) + expect(stakerDataAfter.userBalances.permMultiplier, "permMultiplier After").to.eq(permMultiplierExpected) + expect(stakerDataAfter.userBalances.seasonMultiplier, "seasonMultiplier After").to.eq(seasonMultiplierExpected) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "seasonMultiplier After").to.eq(cooldownMultiplierExpected) + expect(stakerDataAfter.userBalances.raw, "raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "votes after").to.eq(balanceExpected) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(balanceExpected) + }) }) }) - it("should increase a users voting power when they complete said quest") - it("should allow an admin to end the quest season") // Important that each action (checkTimestamp, completeQuest, mint) applies this because // scaledBalance could actually decrease, even in these situations, since old seasonMultipliers are slashed it("should slash an old seasons reward on any action") @@ -565,53 +934,354 @@ describe("Staked Token", () => { context("cooldown", () => { const stakedAmount = simpleToExactAmount(7000) context("with no delegate", () => { + let stakedTimestamp: BN beforeEach(async () => { stakedToken = await redeployStakedToken() await rewardToken.connect(sa.default.signer).approve(stakedToken.address, simpleToExactAmount(10000)) await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) + stakedTimestamp = await getTimestamp() + }) + context("should fail when", () => { + it("nothing staked", async () => { + await expect(stakedToken.connect(sa.dummy1.signer).startCooldown(cooldown100Percentage)).to.revertedWith( + "INVALID_BALANCE_ON_COOLDOWN", + ) + }) + it("0 percentage", async () => { + await expect(stakedToken.startCooldown(0)).to.revertedWith("Invalid percentage") + }) + it("percentage too large", async () => { + await expect(stakedToken.startCooldown(cooldown100Percentage.add(1))).to.revertedWith("Invalid percentage") + }) }) it("should start cooldown", async () => { - const tx = await stakedToken.startCooldown() - await expect(tx).to.emit(stakedToken, "Cooldown").withArgs(sa.default.address) - const currentTime = await getTimestamp() - expect(await stakedToken.stakersCooldowns(sa.default.address), "staked cooldown start").to.eq(currentTime) + const tx = await stakedToken.startCooldown(cooldown100Percentage) + + await expect(tx) + .to.emit(stakedToken, "Cooldown") + .withArgs(sa.default.address, cooldown100Percentage) + + const startCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(startCooldownTimestamp) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(cooldown100Percentage) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + // TODO why is weightedTimestamp 1 second behind lastAction? + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(startCooldownTimestamp.sub(1)) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(startCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(100) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(0) + expect(stakerDataAfter.votes, "votes after").to.eq(0) }) - it("should cooldown again after it has already started", async () => { - // First cooldown - await stakedToken.startCooldown() - await increaseTime(ONE_DAY) + it("should partial cooldown again after it has already started", async () => { + const stakerDataBefore = await snapshotUserStakingData(sa.default.address) + expect(stakerDataBefore.userBalances.weightedTimestamp, "weighted timestamp before").to.eq(stakedTimestamp) + + // First cooldown for 80% of stake + const firstCooldown = simpleToExactAmount(0.8) + await stakedToken.startCooldown(firstCooldown) + + const cooldown1stTimestamp = await getTimestamp() + const stakerDataAfter1stooldown = await snapshotUserStakingData(sa.default.address) + + expect(stakerDataAfter1stooldown.cooldownTimestamp, "cooldown timestamp after 1st").to.eq(cooldown1stTimestamp) + expect(stakerDataAfter1stooldown.cooldownPercentage, "cooldown percentage after 1st").to.eq(firstCooldown) + expect(stakerDataAfter1stooldown.userBalances.raw, "staked raw balance after 1st").to.eq(stakedAmount) + expect(stakerDataAfter1stooldown.userBalances.weightedTimestamp, "weighted timestamp after 1st").to.eq( + cooldown1stTimestamp.sub(1), + ) + expect(stakerDataAfter1stooldown.userBalances.lastAction, "last action after 1st").to.eq(cooldown1stTimestamp) + expect(stakerDataAfter1stooldown.userBalances.permMultiplier, "perm multiplier after 1st").to.eq(0) + expect(stakerDataAfter1stooldown.userBalances.seasonMultiplier, "season multiplier after 1st").to.eq(0) + expect(stakerDataAfter1stooldown.userBalances.timeMultiplier, "time multiplier after 1st").to.eq(0) + expect(stakerDataAfter1stooldown.userBalances.cooldownMultiplier, "cooldown multiplier after 1st").to.eq(80) + expect(stakerDataAfter1stooldown.stakedBalance, "staked balance after 1st").to.eq(stakedAmount.div(5)) - // Second cooldown - await stakedToken.startCooldown() + await increaseTime(ONE_DAY) - const currentTime = await getTimestamp() - expect(await stakedToken.stakersCooldowns(sa.default.address), "staker cooldown after").to.eq(currentTime) + // Second cooldown for only 20% of stake + const secondCooldown = simpleToExactAmount(0.2) + await stakedToken.startCooldown(secondCooldown) + + const cooldown2ndTimestamp = await getTimestamp() + const stakerDataAfter2ndCooldown = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter2ndCooldown.cooldownTimestamp, "cooldown timestamp after 2nd").to.eq(cooldown2ndTimestamp) + expect(stakerDataAfter2ndCooldown.cooldownPercentage, "cooldown percentage after 2nd").to.eq(secondCooldown) + expect(stakerDataAfter2ndCooldown.userBalances.raw, "staked raw balance after 2nd").to.eq(stakedAmount) + expect(stakerDataAfter2ndCooldown.userBalances.weightedTimestamp, "weighted timestamp after 2nd").to.eq(stakedTimestamp) + expect(stakerDataAfter2ndCooldown.userBalances.lastAction, "last action after 2nd").to.eq(cooldown2ndTimestamp) + expect(stakerDataAfter2ndCooldown.userBalances.permMultiplier, "perm multiplier after 2nd").to.eq(0) + expect(stakerDataAfter2ndCooldown.userBalances.seasonMultiplier, "season multiplier after 2nd").to.eq(0) + expect(stakerDataAfter2ndCooldown.userBalances.timeMultiplier, "time multiplier after 2nd").to.eq(0) + expect(stakerDataAfter2ndCooldown.userBalances.cooldownMultiplier, "cooldown multiplier after 2nd").to.eq(20) + expect(stakerDataAfter2ndCooldown.stakedBalance, "staked balance after 2nd").to.eq(stakedAmount.mul(4).div(5)) }) - it("should fail when nothing staked", async () => { - await expect(stakedToken.connect(sa.dummy1.signer).startCooldown()).to.revertedWith("INVALID_BALANCE_ON_COOLDOWN") + it("should reduce cooldown percentage enough to end the cooldown") + context("should end 100% cooldown", () => { + beforeEach(async () => { + await increaseTime(ONE_WEEK) + await stakedToken.startCooldown(cooldown100Percentage) + }) + it("in cooldown", async () => { + await increaseTime(ONE_DAY) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount) + }) + it("in unstake window", async () => { + await increaseTime(ONE_DAY.mul(8)) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount) + }) + it("after unstake window", async () => { + await increaseTime(ONE_DAY.mul(12)) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount) + }) + it("after time multiplier increases", async () => { + await increaseTime(ONE_WEEK.mul(14)) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(20) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount.mul(12).div(10)) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount.mul(12).div(10)) + }) }) - it.skip("should proportionally reset cooldown when staking in cooldown", async () => { - await stakedToken.startCooldown() - const stakerCooldownBefore = await stakedToken.stakersCooldowns(sa.default.address) - const currentTime = await getTimestamp() - expect(await stakedToken.stakersCooldowns(sa.default.address), "staker cooldown after 1st stake").to.eq(currentTime) + context("should end partial cooldown", () => { + beforeEach(async () => { + await increaseTime(ONE_WEEK) + await stakedToken.startCooldown(simpleToExactAmount(0.3)) + }) + it("in cooldown", async () => { + await increaseTime(ONE_DAY) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount) + }) + it("in unstake window", async () => { + await increaseTime(ONE_DAY.mul(8)) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount) + }) + it("after unstake window", async () => { + await increaseTime(ONE_DAY.mul(12)) + const tx = await stakedToken.endCooldown() + + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const endCooldownTimestamp = await getTimestamp() + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "cooldown timestamp after").to.eq(0) + expect(stakerDataAfter.cooldownPercentage, "cooldown percentage after").to.eq(0) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after").to.eq(stakedAmount) + expect(stakerDataAfter.userBalances.weightedTimestamp, "weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "last action after").to.eq(endCooldownTimestamp) + expect(stakerDataAfter.userBalances.permMultiplier, "perm multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.seasonMultiplier, "season multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.timeMultiplier, "time multiplier after").to.eq(0) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(stakerDataAfter.stakedBalance, "staked balance after").to.eq(stakedAmount) + expect(stakerDataAfter.votes, "staked votes after").to.eq(stakedAmount) + }) + }) + context("should end partial cooldown", () => { + it("in cooldown") + it("in unstake window") + it("after unstake window") + }) + it("should end partial cooldown via staking", async () => { + // skip ahead 4 weeks + await increaseTime(ONE_WEEK.mul(4)) + + // Cooldown 80% of stake so only 20% of their voting power remains + const cooldownPercentage = simpleToExactAmount(0.8) + await stakedToken.startCooldown(cooldownPercentage) + const cooldownTimestamp = await getTimestamp() + + const stakerDataAfterCooldown = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfterCooldown.cooldownTimestamp, "cooldown timestamp after cooldown").to.eq(cooldownTimestamp) + expect(stakerDataAfterCooldown.cooldownPercentage, "cooldown percentage after cooldown").to.eq(cooldownPercentage) + expect(stakerDataAfterCooldown.userBalances.raw, "staked raw balance after cooldown").to.eq(stakedAmount) + expect(stakerDataAfterCooldown.userBalances.weightedTimestamp, "weighted timestamp after cooldown").to.eq(stakedTimestamp) + expect(stakerDataAfterCooldown.userBalances.lastAction, "last action after cooldown").to.eq(cooldownTimestamp) + expect(stakerDataAfterCooldown.stakedBalance, "staked after cooldown").to.eq(stakedAmount.div(5)) + expect(stakerDataAfterCooldown.userBalances.cooldownMultiplier, "cooldown multiplier after cooldown").to.eq(80) + expect(stakerDataAfterCooldown.userBalances.timeMultiplier, "time multiplier after cooldown").to.eq(0) + expect(stakerDataAfterCooldown.votes, "20% of vote after 80% cooldown").to.eq(stakedAmount.div(5)) + + // Stake 3000 on top of 7000 and end cooldown + const secondStakeAmount = simpleToExactAmount(3000) + const tx = await stakedToken["stake(uint256,bool)"](secondStakeAmount, true) + const secondStakedTimestamp = await getTimestamp() + + await expect(tx) + .to.emit(stakedToken, "Staked") + .withArgs(sa.default.address, secondStakeAmount, ZERO_ADDRESS) + await expect(tx).to.emit(stakedToken, "DelegateChanged").not + await expect(tx) + .to.emit(stakedToken, "DelegateVotesChanged") + .withArgs(sa.default.address, stakedAmount.div(5), stakedAmount.add(secondStakeAmount)) + await expect(tx) + .to.emit(rewardToken, "Transfer") + .withArgs(sa.default.address, stakedToken.address, secondStakeAmount) + await expect(tx) + .to.emit(stakedToken, "CooldownExited") + .withArgs(sa.default.address) + + const stakerDataAfter2ndStake = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter2ndStake.cooldownTimestamp, "cooldown timestamp after 2nd stake").to.eq(0) + expect(stakerDataAfter2ndStake.cooldownPercentage, "cooldown percentage after 2nd stake").to.eq(0) + expect(stakerDataAfter2ndStake.userBalances.raw, "staked raw balance after 2nd stake").to.eq( + stakedAmount.add(secondStakeAmount), + ) + // TODO need to calculate the weightedTimestamp = + console.log(`1st staked ${new Date(stakedTimestamp.toNumber() * 1000)}`) + console.log(`cooldown ${new Date(cooldownTimestamp.toNumber() * 1000)}`) + console.log(`2nd staked ${new Date(secondStakedTimestamp.toNumber() * 1000)}`) + console.log(`weighted timestamp ${new Date(stakerDataAfter2ndStake.userBalances.weightedTimestamp * 1000)}`) + // expect(stakerDataAfter2ndStake.userBalances.weightedTimestamp, "weighted timestamp after 2nd stake").to.eq(stakedTimestamp) + expect(stakerDataAfter2ndStake.userBalances.lastAction, "last action after 2nd stake").to.eq(secondStakedTimestamp) + expect(stakerDataAfter2ndStake.userBalances.timeMultiplier, "time multiplier after 2nd stake").to.eq(0) + expect(stakerDataAfter2ndStake.userBalances.cooldownMultiplier, "cooldown multiplier after 2nd stake").to.eq(0) + expect(stakerDataAfter2ndStake.stakedBalance, "staked after 2nd stake").to.eq(stakedAmount.add(secondStakeAmount)) + expect(stakerDataAfter2ndStake.votes, "vote after 2nd stake").to.eq(stakedAmount.add(secondStakeAmount)) + }) + it("should proportionally reset cooldown when staking in cooldown", async () => { + await increaseTime(ONE_WEEK) + + // Staker cooldown 100% of stake + await stakedToken.startCooldown(cooldown100Percentage) + + const stakerDataAfterCooldown = await snapshotUserStakingData(sa.default.address) + const cooldownTime = await getTimestamp() + expect(stakerDataAfterCooldown.cooldownTimestamp, "staker cooldown timestamp after cooldown").to.eq(cooldownTime) await increaseTime(ONE_DAY.mul(5)) - // stake 10x the last stake + // 2nd stake of 3000 on top of the existing 7000 const secondStakeAmount = simpleToExactAmount(3000) await stakedToken["stake(uint256,address)"](secondStakeAmount, sa.default.address) - const currentTimestamp = await getTimestamp() - const secondsAlreadyCooled = currentTimestamp.sub(stakerCooldownBefore) + const secondStakeTimestamp = await getTimestamp() const newStakedAmount = stakedAmount.add(secondStakeAmount) - const weightedSecondsAlreadyCooled = secondsAlreadyCooled.mul(stakedAmount).div(newStakedAmount) - - const stakerCooldownAfter = await stakedToken.stakersCooldowns(sa.default.address) - // new start cooldown = current time - (time already cooled * first staked amount / (first + second staked amount)) - // current time - (5 days * 3000 / (7000 + 3000)) - expect(stakerCooldownAfter, "staker cooldown after 2nd stake").to.eq(currentTimestamp.sub(weightedSecondsAlreadyCooled)) + const stakerDataAfter = await snapshotUserStakingData(sa.default.address) + expect(stakerDataAfter.cooldownTimestamp, "staker cooldown timestamp after 2nd stake").to.eq( + stakerDataAfterCooldown.cooldownTimestamp, + ) + expect(stakerDataAfter.cooldownPercentage, "staker cooldown percentage after 2nd stake").to.eq( + cooldown100Percentage.mul(stakedAmount).div(newStakedAmount), + ) + expect(stakerDataAfter.userBalances.raw, "staked raw balance after 2nd stake").to.eq(newStakedAmount) + expect(stakerDataAfter.stakedBalance, "staker staked after 2nd stake").to.eq(secondStakeAmount) + expect(stakerDataAfter.votes, "staker votes after 2nd stake").to.eq(secondStakeAmount) + // TODO calculate new weighted timestamp + // expect(stakerDataAfter.userBalances.weightedTimestamp, "staker weighted timestamp after").to.eq(stakedTimestamp) + expect(stakerDataAfter.userBalances.lastAction, "staker last action after 2nd stake").to.eq(secondStakeTimestamp) + expect(stakerDataAfter.userBalances.cooldownMultiplier, "staker cooldown multiplier after 2nd stake").to.eq(70) + expect(stakerDataAfter.userBalances.timeMultiplier, "staker time multiplier after 2nd stake").to.eq(0) }) }) context("with delegate", () => { @@ -621,21 +1291,24 @@ describe("Staked Token", () => { await stakedToken["stake(uint256,address)"](stakedAmount, sa.dummy1.address) }) it("should fail by delegate", async () => { - await expect(stakedToken.connect(sa.dummy1.address).startCooldown()).to.revertedWith("INVALID_BALANCE_ON_COOLDOWN") + await expect(stakedToken.connect(sa.dummy1.address).startCooldown(cooldown100Percentage)).to.revertedWith( + "INVALID_BALANCE_ON_COOLDOWN", + ) }) }) }) - context("withdraw", () => { + context.skip("withdraw", () => { const stakedAmount = simpleToExactAmount(2000) - const withdrawAmount = simpleToExactAmount(100) + let cooldownTimestamp: BN context("should not be possible", () => { + const withdrawAmount = simpleToExactAmount(100) beforeEach(async () => { stakedToken = await redeployStakedToken() await rewardToken.connect(sa.default.signer).approve(stakedToken.address, stakedAmount) await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) }) it("with zero balance", async () => { - await stakedToken.startCooldown() + await stakedToken.startCooldown(cooldown100Percentage) await increaseTime(ONE_DAY.mul(7).add(60)) await expect(stakedToken.withdraw(0, sa.default.address, false, false)).to.revertedWith("INVALID_ZERO_AMOUNT") }) @@ -645,66 +1318,165 @@ describe("Staked Token", () => { ) }) it("before cooldown finished", async () => { - await stakedToken.startCooldown() + await stakedToken.startCooldown(cooldown100Percentage) await increaseTime(ONE_DAY.mul(7).sub(60)) await expect(stakedToken.withdraw(withdrawAmount, sa.default.address, false, false)).to.revertedWith( "INSUFFICIENT_COOLDOWN", ) }) it("after the unstake window", async () => { - await stakedToken.startCooldown() + await stakedToken.startCooldown(cooldown100Percentage) await increaseTime(ONE_DAY.mul(9).add(60)) await expect(stakedToken.withdraw(withdrawAmount, sa.default.address, false, false)).to.revertedWith( "UNSTAKE_WINDOW_FINISHED", ) }) it("when withdrawing too much", async () => { - await stakedToken.startCooldown() + await stakedToken.startCooldown(10000) await increaseTime(ONE_DAY.mul(7).add(60)) await expect(stakedToken.withdraw(stakedAmount.add(1), sa.default.address, false, false)).to.reverted }) }) - context("with no delegate, after cooldown and in unstake window", () => { + context("with no delegate, after 100% cooldown and in unstake window", () => { let beforeData: UserStakingData beforeEach(async () => { stakedToken = await redeployStakedToken() await rewardToken.connect(sa.default.signer).approve(stakedToken.address, stakedAmount) await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) - await stakedToken.startCooldown() + await stakedToken.startCooldown(simpleToExactAmount(1)) + cooldownTimestamp = await getTimestamp() await increaseTime(ONE_DAY.mul(7).add(60)) - beforeData = await snapshotUserStakingData(sa.default) + beforeData = await snapshotUserStakingData(sa.default.address) + expect(beforeData.userBalances.raw, "staked raw balance before").to.eq(stakedAmount) + expect(beforeData.stakedBalance, "staker staked before").to.eq(0) + expect(beforeData.votes, "staker votes before").to.eq(0) + expect(beforeData.rewardsBalance, "staker rewards before").to.eq(startingMintAmount.sub(stakedAmount)) + expect(beforeData.cooldownTimestamp, "cooldown timestamp before").to.eq(cooldownTimestamp) + expect(beforeData.cooldownPercentage, "cooldown percentage before").to.eq(simpleToExactAmount(1)) + expect(beforeData.userBalances.cooldownMultiplier, "cooldown multiplier before").to.eq(100) }) - it.skip("partial withdraw not including fee", async () => { + it("partial withdraw not including fee", async () => { + const withdrawAmount = simpleToExactAmount(100) + const redemptionFee = withdrawAmount.div(10) const tx2 = await stakedToken.withdraw(withdrawAmount, sa.default.address, false, false) - await expect(tx2).to.emit(stakedToken, "Withdraw").withArgs(sa.default.address, sa.default.address, withdrawAmount) + await expect(tx2) + .to.emit(stakedToken, "Withdraw") + .withArgs(sa.default.address, sa.default.address, withdrawAmount) - const afterData = await snapshotUserStakingData(sa.default) - // TODO calculate withdraw fee - // expect(afterData.stakedBalance).to.eq(beforeData.stakedBalance.sub(withdrawAmount)) - // expect(afterData.votes).to.eq(beforeData.votes.sub(withdrawAmount)) + const afterData = await snapshotUserStakingData(sa.default.address) + expect(afterData.stakedBalance, "staker staked after").to.eq(0) + expect(afterData.votes, "staker votes after").to.eq(0) + expect(afterData.cooldownTimestamp, "cooldown timestamp after").to.eq(beforeData.cooldownTimestamp) + expect(afterData.cooldownPercentage, "cooldown percentage after").to.eq(beforeData.cooldownPercentage) + expect(afterData.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(100) + expect(afterData.userBalances.raw, "staked raw balance after").to.eq(stakedAmount.sub(withdrawAmount).sub(redemptionFee)) expect(afterData.rewardsBalance, "staker rewards after").to.eq(beforeData.rewardsBalance.add(withdrawAmount)) - expect(afterData.stakersCooldown, "staker cooldown after").to.eq(beforeData.stakersCooldown) }) - it("full withdraw including fee", async () => { - const tx = await stakedToken.startCooldown() - await expect(tx).to.emit(stakedToken, "Cooldown").withArgs(sa.default.address) - const currentTimestamp = await getTimestamp() - expect(await stakedToken.stakersCooldowns(sa.default.address), "staked cooldown start").to.eq(currentTimestamp) + // withdraw with fee = staked withdraw + staked withdraw * 0.1 = 1.1 * staked withdraw + // staked withdraw = withdraw with fee / 1.1 + // fee = staked withdraw * 0.1 + // fee = withdraw with fee / 1.1 * 0.1 = withdraw with fee / 11 + const redemptionFee = stakedAmount.div(11) + const tx2 = await stakedToken.withdraw(stakedAmount, sa.default.address, true, true) + await expect(tx2) + .to.emit(stakedToken, "Withdraw") + .withArgs(sa.default.address, sa.default.address, stakedAmount) + + const afterData = await snapshotUserStakingData(sa.default.address) + expect(afterData.stakedBalance, "staker stkRWD after").to.eq(0) + expect(afterData.votes, "staker votes after").to.eq(0) + expect(afterData.cooldownTimestamp, "staked cooldown start").to.eq(0) + expect(afterData.cooldownPercentage, "staked cooldown percentage").to.eq(0) + expect(afterData.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(afterData.userBalances.raw, "staked raw balance after").to.eq(0) + // expect(afterData.rewardsBalance, "staker rewards after").to.eq( + // beforeData.rewardsBalance.add(stakedAmount).sub(redemptionFee), + // ) + assertBNClose(afterData.rewardsBalance, beforeData.rewardsBalance.add(stakedAmount).sub(redemptionFee), 1) + }) + it("not reset the cooldown timer unless all is all unstaked") + it("apply a redemption fee which is added to the pendingRewards from the rewards contract") + it("distribute these pendingAdditionalReward with the next notification") + }) + context("with no delegate, after 70% cooldown and in unstake window", () => { + let beforeData: UserStakingData + // 2000 * 0.3 = 600 + const remainingBalance = stakedAmount.mul(3).div(10) + // 2000 * 0.7 = 1400 + const cooldownAmount = stakedAmount.mul(7).div(10) + beforeEach(async () => { + stakedToken = await redeployStakedToken() + await rewardToken.connect(sa.default.signer).approve(stakedToken.address, stakedAmount) + // Stake 2000 + await stakedToken["stake(uint256,address)"](stakedAmount, sa.default.address) + // Cooldown 70% of 2000 = 1400 + await stakedToken.startCooldown(simpleToExactAmount(0.7)) + cooldownTimestamp = await getTimestamp() await increaseTime(ONE_DAY.mul(7).add(60)) - const tx2 = await stakedToken.withdraw(stakedAmount, sa.default.address, true, false) - await expect(tx2).to.emit(stakedToken, "Withdraw").withArgs(sa.default.address, sa.default.address, stakedAmount) - - const afterData = await snapshotUserStakingData(sa.default) - expect(afterData.stakedBalance, "staker stkRWD after").to.eq(0) - expect(afterData.votes, "staker votes after").to.eq(0) - // TODO calculate withdraw fee - // expect(afterData.rewardsBalance, "staker rewards after").to.eq(beforeData.rewardsBalance.add(stakedAmount)) - expect(afterData.stakersCooldown, "staker cooldown after").to.eq(0) + beforeData = await snapshotUserStakingData(sa.default.address) + expect(beforeData.userBalances.raw, "staked raw balance before").to.eq(stakedAmount) + expect(beforeData.stakedBalance, "staker staked before").to.eq(remainingBalance) + expect(beforeData.votes, "staker votes before").to.eq(remainingBalance) + expect(beforeData.rewardsBalance, "staker rewards before").to.eq(startingMintAmount.sub(stakedAmount)) + expect(beforeData.cooldownTimestamp, "cooldown timestamp before").to.eq(cooldownTimestamp) + expect(beforeData.cooldownPercentage, "cooldown percentage before").to.eq(simpleToExactAmount(0.7)) + expect(beforeData.userBalances.cooldownMultiplier, "cooldown multiplier before").to.eq(70) + }) + it("partial withdraw not including fee", async () => { + const withdrawAmount = simpleToExactAmount(300) + const redemptionFee = withdrawAmount.div(10) + const tx2 = await stakedToken.withdraw(withdrawAmount, sa.default.address, false, false) + await expect(tx2) + .to.emit(stakedToken, "Withdraw") + .withArgs(sa.default.address, sa.default.address, withdrawAmount) + + const afterData = await snapshotUserStakingData(sa.default.address) + console.log("data before") + console.log(beforeData.cooldownPercentage.toString(), beforeData.userBalances.raw.toString()) + console.log(afterData.cooldownPercentage.toString(), afterData.userBalances.raw.toString()) + expect(afterData.stakedBalance, "staker staked after").to.eq(remainingBalance) + expect(afterData.votes, "staker votes after").to.eq(remainingBalance) + expect(afterData.cooldownTimestamp, "cooldown timestamp after").to.eq(beforeData.cooldownTimestamp) + // 1400 / 2000 * 100 = 70 + // (1400 - 300 - 30) / (2000 - 300 - 30) * 1e18 = 64.0718563e16 + const newCooldownPercentage = cooldownAmount + .sub(withdrawAmount) + .sub(redemptionFee) + .mul(simpleToExactAmount(1)) + .div(stakedAmount.sub(withdrawAmount).sub(redemptionFee)) + expect(afterData.cooldownPercentage, "cooldown percentage after").to.eq(newCooldownPercentage) + expect(afterData.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(64) + // 2000 - 300 - 30 = 1670 + expect(afterData.userBalances.raw, "staked raw balance after").to.eq(stakedAmount.sub(withdrawAmount).sub(redemptionFee)) + expect(afterData.rewardsBalance, "staker rewards after").to.eq(beforeData.rewardsBalance.add(withdrawAmount)) + }) + it("full withdraw of cooldown amount including fee", async () => { + // withdraw with fee = staked withdraw + staked withdraw * 0.1 = 1.1 * staked withdraw + // staked withdraw = withdraw with fee / 1.1 + // fee = staked withdraw * 0.1 + // fee = withdraw with fee / 1.1 * 0.1 = withdraw with fee / 11 + const redemptionFee = cooldownAmount.div(11) + const tx2 = await stakedToken.withdraw(cooldownAmount, sa.default.address, true, true) + await expect(tx2) + .to.emit(stakedToken, "Withdraw") + .withArgs(sa.default.address, sa.default.address, cooldownAmount) + + const afterData = await snapshotUserStakingData(sa.default.address) + expect(afterData.stakedBalance, "staker stkRWD after").to.eq(remainingBalance) + expect(afterData.votes, "staker votes after").to.eq(remainingBalance) + expect(afterData.cooldownTimestamp, "staked cooldown start").to.eq(0) + expect(afterData.cooldownPercentage, "staked cooldown percentage").to.eq(0) + expect(afterData.userBalances.cooldownMultiplier, "cooldown multiplier after").to.eq(0) + expect(afterData.userBalances.raw, "staked raw balance after").to.eq(stakedAmount.sub(cooldownAmount)) + // expect(afterData.rewardsBalance, "staker rewards after").to.eq( + // beforeData.rewardsBalance.add(cooldownAmount).sub(redemptionFee), + // ) + assertBNClose(afterData.rewardsBalance, beforeData.rewardsBalance.add(cooldownAmount).sub(redemptionFee), 1) }) it("not reset the cooldown timer unless all is all unstaked") it("apply a redemption fee which is added to the pendingRewards from the rewards contract") diff --git a/test/rewards/boosted-vault.spec.ts b/test/rewards/boosted-vault.spec.ts index 66e8550d..b5b2d5a5 100644 --- a/test/rewards/boosted-vault.spec.ts +++ b/test/rewards/boosted-vault.spec.ts @@ -86,7 +86,7 @@ describe("BoostedVault", async () => { let boostDirector: BoostDirector const maxVMTA = simpleToExactAmount(300000, 18) - const maxBoost = simpleToExactAmount(4, 18) + const maxBoost = simpleToExactAmount(3, 18) const minBoost = simpleToExactAmount(1, 18) const floor = simpleToExactAmount(95, 16) const coeff = BN.from(45) @@ -503,7 +503,7 @@ describe("BoostedVault", async () => { it("should calculate boost for 10k imUSD stake and 250 vMTA", async () => { const deposit = simpleToExactAmount(3333, 14) const stake = simpleToExactAmount(250, 18) - const expectedBoost = simpleToExactAmount(12067, 14) + const expectedBoost = simpleToExactAmount(9999, 14) await expectSuccessfulStake(deposit) await stakingContract.setBalanceOf(sa.default.address, stake) @@ -514,7 +514,7 @@ describe("BoostedVault", async () => { assertBNClosePercent(boost(deposit, calcBoost(deposit, stake, priceCoeffOverride)), expectedBoost, "0.1") const ratio = await savingsVault.getBoost(sa.default.address) - assertBNClosePercent(ratio, simpleToExactAmount(3.621)) + assertBNClosePercent(ratio, simpleToExactAmount(3)) }) // 10k imUSD = 1k $ = 0.33 imBTC it("should calculate boost for 10k imUSD stake and 50 vMTA", async () => { @@ -560,7 +560,7 @@ describe("BoostedVault", async () => { it("should calculate boost for 10k imUSD stake and 250 vMTA", async () => { const deposit = simpleToExactAmount(10000) const stake = simpleToExactAmount(250, 18) - const expectedBoost = simpleToExactAmount(36210) + const expectedBoost = simpleToExactAmount(30000) await expectSuccessfulStake(deposit) await stakingContract.setBalanceOf(sa.default.address, stake) @@ -570,7 +570,7 @@ describe("BoostedVault", async () => { assertBNClosePercent(balance, expectedBoost) assertBNClosePercent(boost(deposit, calcBoost(deposit, stake)), expectedBoost, 0.1) const ratio = await savingsVault.getBoost(sa.default.address) - assertBNClosePercent(ratio, simpleToExactAmount(3.621)) + assertBNClosePercent(ratio, simpleToExactAmount(3)) }) it("should calculate boost for 10k imUSD stake and 50 vMTA", async () => { const deposit = simpleToExactAmount(10000, 18) @@ -700,7 +700,7 @@ describe("BoostedVault", async () => { const aliceData = await snapshotStakingData(alice, alice) const bobData = await snapshotStakingData(bob, bob) - assertBNClosePercent(aliceData.userRewards[1].rate, bobData.userRewards[1].rate.mul(4), "0.1") + assertBNClosePercent(aliceData.userRewards[1].rate, bobData.userRewards[1].rate.mul(3), "0.1") }) }) }) diff --git a/test/savings/savings-manager.spec.ts b/test/savings/savings-manager.spec.ts index 35458946..7071df8d 100644 --- a/test/savings/savings-manager.spec.ts +++ b/test/savings/savings-manager.spec.ts @@ -664,7 +664,7 @@ describe("SavingsManager", async () => { assertBNClose( BN.from(interectCollectedEvent.args.apy), expectedAPY, - simpleToExactAmount(5, 11), // allow for a 0.000005% deviation in the percentage + simpleToExactAmount(1, 12), // allow for a 0.00001 deviation in the percentage ) const lastCollectionAfter = await savingsManager.lastCollection(mUSD.address) assertBNClose(lastCollectionAfter, curTime, BN.from(2)) diff --git a/types/stakedToken.ts b/types/stakedToken.ts new file mode 100644 index 00000000..06e993aa --- /dev/null +++ b/types/stakedToken.ts @@ -0,0 +1,31 @@ +import { BN } from "@utils/math" + +export interface UserBalances { + raw: BN + weightedTimestamp: number + lastAction: number + permMultiplier: number + seasonMultiplier: number + timeMultiplier: number + cooldownMultiplier: number +} + +export interface UserStakingData { + stakedBalance: BN + votes: BN + earnedRewards: BN + cooldownTimestamp: BN + cooldownPercentage: BN + rewardsBalance: BN + userBalances: UserBalances +} + +export enum QuestType { + PERMANENT, + SEASONAL, +} + +export enum QuestStatus { + ACTIVE, + EXPIRED, +} diff --git a/yarn.lock b/yarn.lock index d3c8b5b6..1442ede5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -182,6 +182,18 @@ dependencies: chalk "^4.0.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz#118511f316e2e87ee4294761868e254d3da47960" + integrity sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@ensdomains/ens@^0.4.4": version "0.4.5" resolved "https://registry.yarnpkg.com/@ensdomains/ens/-/ens-0.4.5.tgz#e0aebc005afdc066447c6e22feb4eda89a5edbfc" @@ -266,38 +278,38 @@ patch-package "^6.2.2" postinstall-postinstall "^2.1.0" -"@ethereumjs/block@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-3.3.0.tgz#a1b3baec831c71c0d9e7f6145f25e919cff4939c" - integrity sha512-WoefY9Rs4W8vZTxG9qwntAlV61xsSv0NPoXmHO7x3SH16dwJQtU15YvahPCz4HEEXbu7GgGgNgu0pv8JY7VauA== +"@ethereumjs/block@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-3.4.0.tgz#4747b0c06220ee10cbdfe1cbde8cbb0677b1b074" + integrity sha512-umKAoTX32yXzErpIksPHodFc/5y8bmZMnOl6hWy5Vd8xId4+HKFUOyEiN16Y97zMwFRysRpcrR6wBejfqc6Bmg== dependencies: - "@ethereumjs/common" "^2.3.0" - "@ethereumjs/tx" "^3.2.0" - ethereumjs-util "^7.0.10" + "@ethereumjs/common" "^2.4.0" + "@ethereumjs/tx" "^3.3.0" + ethereumjs-util "^7.1.0" merkle-patricia-tree "^4.2.0" -"@ethereumjs/blockchain@^5.3.0": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/blockchain/-/blockchain-5.3.1.tgz#b8cc506ce2481c32aa7dbb22aa100931e6f3723b" - integrity sha512-Sr39BoTOzmVSnuYzjiCIpgcBUFE5JWcMF0lYCvzrtx/5Lg1tnpZhw9yMQ6JfIomN421epg4oDz99DWlL9Aqz3g== +"@ethereumjs/blockchain@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/blockchain/-/blockchain-5.4.0.tgz#28d712627d3442b2bb1f50dd5acba7cde1021993" + integrity sha512-wAuKLaew6PL52kH8YPXO7PbjjKV12jivRSyHQehkESw4slSLLfYA6Jv7n5YxyT2ajD7KNMPVh7oyF/MU6HcOvg== dependencies: - "@ethereumjs/block" "^3.3.0" - "@ethereumjs/common" "^2.3.1" + "@ethereumjs/block" "^3.4.0" + "@ethereumjs/common" "^2.4.0" "@ethereumjs/ethash" "^1.0.0" debug "^2.2.0" - ethereumjs-util "^7.0.10" + ethereumjs-util "^7.1.0" level-mem "^5.0.1" lru-cache "^5.1.1" rlp "^2.2.4" semaphore-async-await "^1.5.1" -"@ethereumjs/common@^2.3.0", "@ethereumjs/common@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.3.1.tgz#d692e3aff5adb35dd587dd1e6caab69e0ed2fa0b" - integrity sha512-V8hrULExoq0H4HFs3cCmdRGbgmipmlNzak6Xg34nHYfQyqkSdrCuflvYjyWmsNpI8GtrcZhzifAbgX/1C1Cjwg== +"@ethereumjs/common@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.4.0.tgz#2d67f6e6ba22246c5c89104e6b9a119fb3039766" + integrity sha512-UdkhFWzWcJCZVsj1O/H8/oqj/0RVYjLc1OhPjBrQdALAkQHpCp8xXI4WLnuGTADqTdJZww0NtgwG+TRPkXt27w== dependencies: crc-32 "^1.2.0" - ethereumjs-util "^7.0.10" + ethereumjs-util "^7.1.0" "@ethereumjs/ethash@^1.0.0": version "1.0.0" @@ -309,27 +321,27 @@ ethereumjs-util "^7.0.7" miller-rabin "^4.0.0" -"@ethereumjs/tx@^3.2.0", "@ethereumjs/tx@^3.2.1": - version "3.2.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.2.1.tgz#65f5f1c11541764f08377a94ba4b0dcbbd67739e" - integrity sha512-i9V39OtKvwWos1uVNZxdVhd7zFOyzFLjgt69CoiOY0EmXugS0HjO3uxpLBSglDKFMRriuGqw6ddKEv+RP1UNEw== +"@ethereumjs/tx@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.0.tgz#14ed1b7fa0f28e1cd61e3ecbdab824205f6a4378" + integrity sha512-yTwEj2lVzSMgE6Hjw9Oa1DZks/nKTWM8Wn4ykDNapBPua2f4nXO3qKnni86O6lgDj5fVNRqbDsD0yy7/XNGDEA== dependencies: - "@ethereumjs/common" "^2.3.1" - ethereumjs-util "^7.0.10" + "@ethereumjs/common" "^2.4.0" + ethereumjs-util "^7.1.0" -"@ethereumjs/vm@^5.3.2": - version "5.4.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/vm/-/vm-5.4.1.tgz#62d9f64aa5a2fb334ad630418683c9654f683a5a" - integrity sha512-cpQcg5CtjzXJBn8QNiobaiWckeN/ZQwsDHLYa9df2wBEUvzuEZgFWK48YEXSpx3CnUY9fNT/lgA9CzKdq8HTzQ== +"@ethereumjs/vm@^5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/vm/-/vm-5.5.2.tgz#918a2c1000aaa9fdbe6007a4fdc2c62833122adf" + integrity sha512-AydZ4wfvZAsBuFzs3xVSA2iU0hxhL8anXco3UW3oh9maVC34kTEytOfjHf06LTEfN0MF9LDQ4ciLa7If6ZN/sg== dependencies: - "@ethereumjs/block" "^3.3.0" - "@ethereumjs/blockchain" "^5.3.0" - "@ethereumjs/common" "^2.3.1" - "@ethereumjs/tx" "^3.2.1" + "@ethereumjs/block" "^3.4.0" + "@ethereumjs/blockchain" "^5.4.0" + "@ethereumjs/common" "^2.4.0" + "@ethereumjs/tx" "^3.3.0" async-eventemitter "^0.2.4" core-js-pure "^3.0.1" debug "^2.2.0" - ethereumjs-util "^7.0.10" + ethereumjs-util "^7.1.0" functional-red-black-tree "^1.0.1" mcl-wasm "^0.7.1" merkle-patricia-tree "^4.2.0" @@ -394,6 +406,19 @@ "@ethersproject/transactions" "^5.4.0" "@ethersproject/web" "^5.4.0" +"@ethersproject/abstract-provider@5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e" + integrity sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ== + dependencies: + "@ethersproject/bignumber" "^5.4.0" + "@ethersproject/bytes" "^5.4.0" + "@ethersproject/logger" "^5.4.0" + "@ethersproject/networks" "^5.4.0" + "@ethersproject/properties" "^5.4.0" + "@ethersproject/transactions" "^5.4.0" + "@ethersproject/web" "^5.4.0" + "@ethersproject/abstract-signer@5.4.0", "@ethersproject/abstract-signer@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.0.tgz#cd5f50b93141ee9f9f49feb4075a0b3eafb57d65" @@ -405,6 +430,17 @@ "@ethersproject/logger" "^5.4.0" "@ethersproject/properties" "^5.4.0" +"@ethersproject/abstract-signer@5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81" + integrity sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA== + dependencies: + "@ethersproject/abstract-provider" "^5.4.0" + "@ethersproject/bignumber" "^5.4.0" + "@ethersproject/bytes" "^5.4.0" + "@ethersproject/logger" "^5.4.0" + "@ethersproject/properties" "^5.4.0" + "@ethersproject/address@5.4.0", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3" @@ -440,6 +476,15 @@ "@ethersproject/logger" "^5.4.0" bn.js "^4.11.9" +"@ethersproject/bignumber@5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.4.1.tgz#64399d3b9ae80aa83d483e550ba57ea062c1042d" + integrity sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg== + dependencies: + "@ethersproject/bytes" "^5.4.0" + "@ethersproject/logger" "^5.4.0" + bn.js "^4.11.9" + "@ethersproject/bytes@5.4.0", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e" @@ -470,6 +515,22 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/transactions" "^5.4.0" +"@ethersproject/contracts@5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470" + integrity sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w== + dependencies: + "@ethersproject/abi" "^5.4.0" + "@ethersproject/abstract-provider" "^5.4.0" + "@ethersproject/abstract-signer" "^5.4.0" + "@ethersproject/address" "^5.4.0" + "@ethersproject/bignumber" "^5.4.0" + "@ethersproject/bytes" "^5.4.0" + "@ethersproject/constants" "^5.4.0" + "@ethersproject/logger" "^5.4.0" + "@ethersproject/properties" "^5.4.0" + "@ethersproject/transactions" "^5.4.0" + "@ethersproject/hash@5.4.0", "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.0.4", "@ethersproject/hash@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.4.0.tgz#d18a8e927e828e22860a011f39e429d388344ae0" @@ -541,6 +602,13 @@ dependencies: "@ethersproject/logger" "^5.4.0" +"@ethersproject/networks@5.4.2": + version "5.4.2" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35" + integrity sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw== + dependencies: + "@ethersproject/logger" "^5.4.0" + "@ethersproject/pbkdf2@5.4.0", "@ethersproject/pbkdf2@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz#ed88782a67fda1594c22d60d0ca911a9d669641c" @@ -581,6 +649,31 @@ bech32 "1.1.4" ws "7.4.6" +"@ethersproject/providers@5.4.3": + version "5.4.3" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.3.tgz#4cd7ccd9e12bc3875b33df8b24abf735663958a5" + integrity sha512-VURwkaWPoUj7jq9NheNDT5Iyy64Qcyf6BOFDwVdHsmLmX/5prNjFrgSX3GHPE4z1BRrVerDxe2yayvXKFm/NNg== + dependencies: + "@ethersproject/abstract-provider" "^5.4.0" + "@ethersproject/abstract-signer" "^5.4.0" + "@ethersproject/address" "^5.4.0" + "@ethersproject/basex" "^5.4.0" + "@ethersproject/bignumber" "^5.4.0" + "@ethersproject/bytes" "^5.4.0" + "@ethersproject/constants" "^5.4.0" + "@ethersproject/hash" "^5.4.0" + "@ethersproject/logger" "^5.4.0" + "@ethersproject/networks" "^5.4.0" + "@ethersproject/properties" "^5.4.0" + "@ethersproject/random" "^5.4.0" + "@ethersproject/rlp" "^5.4.0" + "@ethersproject/sha2" "^5.4.0" + "@ethersproject/strings" "^5.4.0" + "@ethersproject/transactions" "^5.4.0" + "@ethersproject/web" "^5.4.0" + bech32 "1.1.4" + ws "7.4.6" + "@ethersproject/random@5.4.0", "@ethersproject/random@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.4.0.tgz#9cdde60e160d024be39cc16f8de3b9ce39191e16" @@ -944,10 +1037,10 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== -"@tsconfig/node16@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1" - integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA== +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== "@typechain/ethers-v5@^2.0.0": version "2.0.0" @@ -961,10 +1054,10 @@ resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-7.0.1.tgz#f9ae60ae5bd9e8ea8a996f66244147e8e74034ae" integrity sha512-mXEJ7LG0pOYO+MRPkHtbf30Ey9X2KAsU0wkeoVvjQIn7iAY6tB3k3s+82bbmJAUMyENbQ04RDOZit36CgSG6Gg== -"@typechain/hardhat@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-2.1.0.tgz#e6abe4b44172273f537121ea79afe0bbbbb0bff9" - integrity sha512-f9zv+yGA6P055U8cYruW9UnSH2lBdM2PSJx/oy8biMXdw5d+xVfY4GCet4w6ry32Vy1OjqGoDM98EZ9H0EAPrg== +"@typechain/hardhat@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-2.3.0.tgz#dc7f29281637b38b77c7c046ae82700703395d0f" + integrity sha512-zERrtNol86L4DX60ktnXxP7Cq8rSZHPaQvsChyiQQVuvVs2FTLm24Yi+MYnfsIdbUBIXZG7SxDWhtCF5I0tJNQ== dependencies: fs-extra "^9.1.0" @@ -1059,10 +1152,10 @@ dependencies: "@types/node" "*" -"@types/mocha@^8.2.2": - version "8.2.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" - integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== +"@types/mocha@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" + integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA== "@types/node-fetch@^2.5.5": version "2.5.10" @@ -1072,7 +1165,7 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@^15.12.4": +"@types/node@*": version "15.12.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.5.tgz#9a78318a45d75c9523d2396131bd3cca54b2d185" integrity sha512-se3yX7UHv5Bscf8f1ERKvQOD6sTyycH3hdaoozvaLxgUiY5lIGEeH37AD0G0Qi9kPqihPn0HOfd2yaIEN9VwEg== @@ -1087,6 +1180,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.15.tgz#10ee6a6a3f971966fddfa3f6e89ef7a73ec622df" integrity sha512-F6S4Chv4JicJmyrwlDkxUdGNSplsQdGwp1A0AJloEVDirWdZOAiRHhovDlsFkKUrquUXhz1imJhXHsf59auyAg== +"@types/node@^16.6.0": + version "16.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.0.tgz#0d5685f85066f94e97f19e8a67fe003c5fadacc4" + integrity sha512-OyiZPohMMjZEYqcVo/UJ04GyAxXOJEZO/FpzyXxcH4r/ArrVoXHf4MbUrkLp0Tz7/p1mMKpo5zJ6ZHl8XBNthQ== + "@types/node@^8.0.0": version "8.10.66" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" @@ -1336,6 +1434,11 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-walk@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.1.1.tgz#3ddab7f84e4a7e2313f6c414c5b7dac85f4e3ebc" + integrity sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w== + acorn@^6.0.7: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" @@ -1346,6 +1449,11 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== + address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -2765,22 +2873,7 @@ chokidar@3.3.0: optionalDependencies: fsevents "~2.1.1" -chokidar@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.5.0" - optionalDependencies: - fsevents "~2.3.1" - -chokidar@^3.4.0: +chokidar@3.5.2, chokidar@^3.4.0: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -3388,24 +3481,24 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -defender-base-client@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/defender-base-client/-/defender-base-client-1.7.0.tgz#258033cde58ed7f256d721f77b7b30e49d4d680b" - integrity sha512-snNyKtxUZn8y0JewVAQDJUam1qESFnkJiOUNtutB07OzldUoA7R/n/xuu5LyM3z4ScvoXu6xeVe67xs2H60KKg== +defender-base-client@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/defender-base-client/-/defender-base-client-1.10.0.tgz#d85af9e84f57d6201e7741e44e59d5a3ff9c2b75" + integrity sha512-PvjJj4pWug/PVZWVi6jrdXGpykgtifGdomV1ePgZSj7cjK2mSbS0P3GUUeIzCA/d5HSV5/L6reZkI1KavnSdpA== dependencies: amazon-cognito-identity-js "^4.3.3" axios "^0.19.2" lodash "^4.17.19" node-fetch "^2.6.0" -defender-relay-client@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/defender-relay-client/-/defender-relay-client-1.8.0.tgz#0e81b8667a600c03cb0b18053e70a503e3c04512" - integrity sha512-Ed37zcKCnNkyTE3Y2Re8c8eBP0ufU2jng8CyQ+5KPSGZqs3RVXZmJPLO4ADDWQtnE2ARb6Z3cpMmVm5Exwykog== +defender-relay-client@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/defender-relay-client/-/defender-relay-client-1.10.0.tgz#0fc4ddfbfd44f5012d97dd2af3192c5ccaaf8b51" + integrity sha512-AmBPKiZaC9wF3lf8tfkn/gh6Xs38MKkVh+E47rGlJ5SOL9FRs3sZBmDrkPDmsSi0R1ZPVq3Wg2YFThpa+jUswg== dependencies: amazon-cognito-identity-js "^4.3.3" axios "^0.19.2" - defender-base-client "^1.7.0" + defender-base-client "1.10.0" lodash "^4.17.19" node-fetch "^2.6.0" @@ -4493,6 +4586,18 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.7: ethjs-util "0.1.6" rlp "^2.2.4" +ethereumjs-util@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz#e2b43a30bfcdbcb432a4eb42bd5f2393209b3fd5" + integrity sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" + rlp "^2.2.4" + ethereumjs-vm@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-4.2.0.tgz#e885e861424e373dbc556278f7259ff3fca5edab" @@ -4568,7 +4673,7 @@ ethers@^4.0.32, ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.2, ethers@^5.3.1: +ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.2: version "5.4.0" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.0.tgz#e9fe4b39350bcce5edd410c70bd57ba328ecf474" integrity sha512-hqN1x0CV8VMpQ25WnNEjaMqtB3nA4DRAb2FSmmNaUbD1dF6kWbHs8YaXbVvD37FCg3GTEyc4rV9Pxafk1ByHKw== @@ -4604,6 +4709,42 @@ ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.2, ethers@^5.3.1: "@ethersproject/web" "5.4.0" "@ethersproject/wordlists" "5.4.0" +ethers@^5.4.4: + version "5.4.4" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.4.tgz#35cce530505b84c699da944162195cfb3f894947" + integrity sha512-zaTs8yaDjfb0Zyj8tT6a+/hEkC+kWAA350MWRp6yP5W7NdGcURRPMOpOU+6GtkfxV9wyJEShWesqhE/TjdqpMA== + dependencies: + "@ethersproject/abi" "5.4.0" + "@ethersproject/abstract-provider" "5.4.1" + "@ethersproject/abstract-signer" "5.4.1" + "@ethersproject/address" "5.4.0" + "@ethersproject/base64" "5.4.0" + "@ethersproject/basex" "5.4.0" + "@ethersproject/bignumber" "5.4.1" + "@ethersproject/bytes" "5.4.0" + "@ethersproject/constants" "5.4.0" + "@ethersproject/contracts" "5.4.1" + "@ethersproject/hash" "5.4.0" + "@ethersproject/hdnode" "5.4.0" + "@ethersproject/json-wallets" "5.4.0" + "@ethersproject/keccak256" "5.4.0" + "@ethersproject/logger" "5.4.0" + "@ethersproject/networks" "5.4.2" + "@ethersproject/pbkdf2" "5.4.0" + "@ethersproject/properties" "5.4.0" + "@ethersproject/providers" "5.4.3" + "@ethersproject/random" "5.4.0" + "@ethersproject/rlp" "5.4.0" + "@ethersproject/sha2" "5.4.0" + "@ethersproject/signing-key" "5.4.0" + "@ethersproject/solidity" "5.4.0" + "@ethersproject/strings" "5.4.0" + "@ethersproject/transactions" "5.4.0" + "@ethersproject/units" "5.4.0" + "@ethersproject/wallet" "5.4.0" + "@ethersproject/web" "5.4.0" + "@ethersproject/wordlists" "5.4.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -5174,7 +5315,7 @@ fsevents@~2.1.1: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== -fsevents@~2.3.1, fsevents@~2.3.2: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -5472,10 +5613,10 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -graphql-request@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.4.0.tgz#3a400cd5511eb3c064b1873afb059196bbea9c2b" - integrity sha512-acrTzidSlwAj8wBNO7Q/UQHS8T+z5qRGquCQRv9J1InwR01BBWV9ObnoE+JS5nCCEj8wSGS0yrDXVDoRiKZuOg== +graphql-request@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.5.0.tgz#7e69574e15875fb3f660a4b4be3996ecd0bbc8b7" + integrity sha512-Io89QpfU4rqiMbqM/KwMBzKaDLOppi8FU8sEccCE4JqCgz95W9Q8bvxQ4NfPALLSMvg9nafgg8AkYRmgKSlukA== dependencies: cross-fetch "^3.0.6" extract-files "^9.0.0" @@ -5534,16 +5675,16 @@ hardhat-gas-reporter@^1.0.1: eth-gas-reporter "^0.2.20" sha1 "^1.1.1" -hardhat@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.4.1.tgz#2cd1e86ee6ca3a6a473eeb0f55bd3124c8c59250" - integrity sha512-vwllrFypukeE/Q+4ZfWj7j7nUo4ncUhRpsAYUM0Ruuuk6pQlKmRa0A6c0kxRSvvVgQsMud6j+/weYhbMX1wPmQ== - dependencies: - "@ethereumjs/block" "^3.3.0" - "@ethereumjs/blockchain" "^5.3.0" - "@ethereumjs/common" "^2.3.1" - "@ethereumjs/tx" "^3.2.1" - "@ethereumjs/vm" "^5.3.2" +hardhat@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.6.0.tgz#a00a44d36559a880c170dca6363cc33f7545364b" + integrity sha512-NEM2pe11QXWXB7k49heOLQA9vxihG4DJ0712KjMT9NYSZgLOMcWswJ3tvn+/ND6vzLn6Z4pqr2x/kWSfllWFuw== + dependencies: + "@ethereumjs/block" "^3.4.0" + "@ethereumjs/blockchain" "^5.4.0" + "@ethereumjs/common" "^2.4.0" + "@ethereumjs/tx" "^3.3.0" + "@ethereumjs/vm" "^5.5.2" "@ethersproject/abi" "^5.1.2" "@sentry/node" "^5.18.1" "@solidity-parser/parser" "^0.11.0" @@ -5561,7 +5702,7 @@ hardhat@^2.4.0: eth-sig-util "^2.5.2" ethereum-cryptography "^0.1.2" ethereumjs-abi "^0.6.8" - ethereumjs-util "^7.0.10" + ethereumjs-util "^7.1.0" find-up "^2.1.0" fp-ts "1.19.3" fs-extra "^7.0.1" @@ -6503,6 +6644,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -7427,15 +7575,15 @@ mocha@^7.1.1, mocha@^7.1.2: yargs-parser "13.1.2" yargs-unparser "1.6.0" -mocha@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.0.1.tgz#01e66b7af0012330c0a38c4b6eaa6d92b8a81bf9" - integrity sha512-9zwsavlRO+5csZu6iRtl3GHImAbhERoDsZwdRkdJ/bE+eVplmoxNKE901ZJ9LdSchYBjSCPbjKc5XvcAri2ylw== +mocha@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.0.3.tgz#128cd6bbd3ee0adcdaef715f357f76ec1e6227c7" + integrity sha512-hnYFrSefHxYS2XFGtN01x8un0EwNu2bzKvhpRFhgoybIvMaOkkL60IVPmkb5h6XDmUl4IMSB+rT5cIO4/4bJgg== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.5.1" + chokidar "3.5.2" debug "4.3.1" diff "5.0.0" escape-string-regexp "4.0.0" @@ -7448,12 +7596,12 @@ mocha@^9.0.1: minimatch "3.0.4" ms "2.1.3" nanoid "3.1.23" - serialize-javascript "5.0.1" + serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" which "2.0.2" wide-align "1.1.3" - workerpool "6.1.4" + workerpool "6.1.5" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" @@ -8684,13 +8832,6 @@ readdirp@~3.2.0: dependencies: picomatch "^2.0.4" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== - dependencies: - picomatch "^2.2.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -9181,10 +9322,10 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-javascript@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" @@ -9530,7 +9671,7 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" -source-map-support@^0.5.13, source-map-support@^0.5.17: +source-map-support@^0.5.13: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -10146,22 +10287,33 @@ ts-generator@^0.1.1: resolve "^1.8.1" ts-essentials "^1.0.0" -ts-node@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be" - integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg== +ts-node@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.0.tgz#f1e88249a00e26aa95e9a93c50f70241a8a1c4bb" + integrity sha512-FstYHtQz6isj8rBtYMN4bZdnXN1vq4HCbqn9vdNQcInRqtB86PePJQIxE6es0PhxKWhj2PHuwbG40H+bxkZPmg== dependencies: + "@cspotcode/source-map-support" "0.6.1" "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.1" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" arg "^4.1.0" create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.17" yn "3.1.1" +tsconfig-paths@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz#79ae67a68c15289fdf5c51cb74f397522d795ed7" + integrity sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q== + dependencies: + json5 "^2.2.0" + minimist "^1.2.0" + strip-bom "^3.0.0" + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -10291,10 +10443,10 @@ typechain@^3.0.0: ts-essentials "^6.0.3" ts-generator "^0.1.1" -typechain@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.1.1.tgz#2e54bee69bb91d21096943deb400aa91638f698e" - integrity sha512-zN4ylCc3gSw2BXy5Zlpmw8q0lf5pQ+U8gYd5WCWRfDVuUkKyZBURz31VLjkPY/nfOr+P6EcwTC4Ae5VNVqrv/g== +typechain@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.1.2.tgz#c8784d6155a8e69397ca47f438a3b4fb2aa939da" + integrity sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA== dependencies: "@types/prettier" "^2.1.1" command-line-args "^4.0.7" @@ -10324,6 +10476,11 @@ typescript@^4.3.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc" integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== +typescript@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" + integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== + typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" @@ -11164,10 +11321,10 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -workerpool@6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.4.tgz#6a972b6df82e38d50248ee2820aa98e2d0ad3090" - integrity sha512-jGWPzsUqzkow8HoAvqaPWTUPCrlPJaJ5tY8Iz7n1uCz3tTp6s3CDG0FF1NsX42WNlkRSW6Mr+CDZGnNoSsKa7g== +workerpool@6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" + integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== wrap-ansi@^2.0.0: version "2.1.0"