Overview
ETH Balance
0 ETH
Eth Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
StakedTokenMTA
Compiler Version
v0.8.6+commit.11564f7e
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
pragma abicoder v2;
import { StakedToken } from "./StakedToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title StakedTokenMTA
* @dev Derives from StakedToken, and simply adds the functionality specific to the $MTA staking token,
* for example compounding rewards.
**/
contract StakedTokenMTA is StakedToken {
using SafeERC20 for IERC20;
/**
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @param _stakedToken Core token that is staked and tracked (e.g. MTA)
* @param _cooldownSeconds Seconds a user must wait after she initiates her cooldown before withdrawal is possible
* @param _unstakeWindow Window in which it is possible to withdraw, following the cooldown period
*/
constructor(
address _nexus,
address _rewardsToken,
address _questManager,
address _stakedToken,
uint256 _cooldownSeconds,
uint256 _unstakeWindow
)
StakedToken(
_nexus,
_rewardsToken,
_questManager,
_stakedToken,
_cooldownSeconds,
_unstakeWindow,
false
)
{}
function initialize(
bytes32 _nameArg,
bytes32 _symbolArg,
address _rewardsDistributorArg
) external initializer {
__StakedToken_init(_nameArg, _symbolArg, _rewardsDistributorArg);
}
/**
* @dev Allows a staker to compound their rewards IF the Staking token and the Rewards token are the same
* for example, with $MTA as both staking token and rewards token. Calls 'claimRewards' on the HeadlessStakingRewards
* before executing a stake here
*/
function compoundRewards() external nonReentrant {
require(address(STAKED_TOKEN) == address(REWARDS_TOKEN), "Only for same pairs");
// 1. claim rewards
uint256 balBefore = STAKED_TOKEN.balanceOf(address(this));
_claimReward(address(this));
// 2. check claim amount
uint256 balAfter = STAKED_TOKEN.balanceOf(address(this));
uint256 claimed = balAfter - balBefore;
require(claimed > 0, "Must compound something");
// 3. re-invest
_settleStake(claimed, address(0), false);
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
pragma abicoder v2;
import { IStakedToken } from "./interfaces/IStakedToken.sol";
import { GamifiedVotingToken } from "./GamifiedVotingToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Root } from "../../shared/Root.sol";
import { InitializableReentrancyGuard } from "../../shared/InitializableReentrancyGuard.sol";
import "./deps/GamifiedTokenStructs.sol";
/**
* @title StakedToken
* @notice StakedToken is a non-transferrable ERC20 token that allows users to stake and withdraw, earning voting rights.
* Scaled balance is determined by quests a user completes, and the length of time they keep the raw balance wrapped.
* Stakers can unstake, after the elapsed cooldown period, and before the end of the unstake window. Users voting/earning
* power is slashed during this time, and they may face a redemption fee if they leave early.
* The reason for this unstake window is that this StakedToken acts as a source of insurance value for the mStable system,
* which can access the funds via the Recollateralisation module, up to the amount defined in `safetyData`.
* Voting power can be used for a number of things: voting in the mStable DAO/emission dials, boosting rewards, earning
* rewards here. While a users "balance" is unique to themselves, they can choose to delegate their voting power (which will apply
* to voting in the mStable DAO and emission dials).
* @author mStable
* @dev Only whitelisted contracts can communicate with this contract, in order to avoid having tokenised wrappers that
* could potentially circumvent our unstaking procedure.
**/
contract StakedToken is GamifiedVotingToken, InitializableReentrancyGuard {
using SafeERC20 for IERC20;
/// @notice Core token that is staked and tracked (e.g. MTA)
IERC20 public immutable STAKED_TOKEN;
/// @notice Seconds a user must wait after she initiates her cooldown before withdrawal is possible
uint256 public immutable COOLDOWN_SECONDS;
/// @notice Window in which it is possible to withdraw, following the cooldown period
uint256 public immutable UNSTAKE_WINDOW;
/// @notice A week
uint256 private constant ONE_WEEK = 7 days;
struct SafetyData {
/// Percentage of collateralisation where 100% = 1e18
uint128 collateralisationRatio;
/// Slash % where 100% = 1e18
uint128 slashingPercentage;
}
/// @notice Data relating to the re-collateralisation safety module
SafetyData public safetyData;
/// @notice Whitelisted smart contract integrations
mapping(address => bool) public whitelistedWrappers;
event Staked(address indexed user, uint256 amount, address delegatee);
event Withdraw(address indexed user, address indexed to, uint256 amount);
event Cooldown(address indexed user, uint256 percentage);
event CooldownExited(address indexed user);
event SlashRateChanged(uint256 newRate);
event Recollateralised();
event WrapperWhitelisted(address wallet);
event WrapperBlacklisted(address wallet);
/***************************************
INIT
****************************************/
/**
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @param _questManager Centralised manager of quests
* @param _stakedToken Core token that is staked and tracked (e.g. MTA)
* @param _cooldownSeconds Seconds a user must wait after she initiates her cooldown before withdrawal is possible
* @param _unstakeWindow Window in which it is possible to withdraw, following the cooldown period
* @param _hasPriceCoeff true if raw staked amount is multiplied by price coeff to get staked amount. eg BPT Staked Token
*/
constructor(
address _nexus,
address _rewardsToken,
address _questManager,
address _stakedToken,
uint256 _cooldownSeconds,
uint256 _unstakeWindow,
bool _hasPriceCoeff
) GamifiedVotingToken(_nexus, _rewardsToken, _questManager, _hasPriceCoeff) {
STAKED_TOKEN = IERC20(_stakedToken);
COOLDOWN_SECONDS = _cooldownSeconds;
UNSTAKE_WINDOW = _unstakeWindow;
}
/**
* @param _nameArg Token name
* @param _symbolArg Token symbol
* @param _rewardsDistributorArg mStable Rewards Distributor
*/
function __StakedToken_init(
bytes32 _nameArg,
bytes32 _symbolArg,
address _rewardsDistributorArg
) public initializer {
__GamifiedToken_init(_nameArg, _symbolArg, _rewardsDistributorArg);
_initializeReentrancyGuard();
safetyData = SafetyData({ collateralisationRatio: 1e18, slashingPercentage: 0 });
}
/**
* @dev Only the recollateralisation module, as specified in the mStable Nexus, can execute this
*/
modifier onlyRecollateralisationModule() {
require(_msgSender() == _recollateraliser(), "Only Recollateralisation Module");
_;
}
/**
* @dev This protects against fn's being called after a recollateralisation event, when the contract is essentially finished
*/
modifier onlyBeforeRecollateralisation() {
_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()], "Not a whitelisted contract");
}
}
/***************************************
ACTIONS
****************************************/
/**
* @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
*/
function stake(uint256 _amount) external {
_transferAndStake(_amount, address(0), false);
}
/**
* @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 end any outstanding cooldown and
* return the user back to their full voting power
*/
function stake(uint256 _amount, bool _exitCooldown) external {
_transferAndStake(_amount, address(0), _exitCooldown);
}
/**
* @dev Stake an `_amount` of STAKED_TOKEN in the system. This amount is added to the users stake and
* boosts their voting power. Take the opportunity to change delegatee.
* @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
*/
function stake(uint256 _amount, address _delegatee) external {
_transferAndStake(_amount, _delegatee, false);
}
/**
* @dev Transfers tokens from sender before calling `_settleStake`
*/
function _transferAndStake(
uint256 _amount,
address _delegatee,
bool _exitCooldown
) internal {
STAKED_TOKEN.safeTransferFrom(_msgSender(), address(this), _amount);
_settleStake(_amount, _delegatee, _exitCooldown);
}
/**
* @dev Internal stake fn. Can only be called by whitelisted contracts/EOAs and only before a recollateralisation event.
* 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 end any outstanding cooldown and
* return the user back to their full voting power
*/
function _settleStake(
uint256 _amount,
address _delegatee,
bool _exitCooldown
) internal onlyBeforeRecollateralisation assertNotContract {
require(_amount != 0, "INVALID_ZERO_AMOUNT");
// 1. Apply the delegate if it has been chosen (else it defaults to the sender)
if (_delegatee != address(0)) {
_delegate(_msgSender(), _delegatee);
}
// 2. Deal with cooldown
// If a user is currently in a cooldown period, re-calculate their cooldown timestamp
Balance memory oldBalance = _balances[_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 ||
(oldBalance.cooldownTimestamp > 0 &&
block.timestamp >
(oldBalance.cooldownTimestamp + COOLDOWN_SECONDS + UNSTAKE_WINDOW));
if (exitCooldown) {
emit CooldownExited(_msgSender());
}
// 3. Settle the stake by depositing the STAKED_TOKEN and minting voting power
_mintRaw(_msgSender(), _amount, exitCooldown);
emit Staked(_msgSender(), _amount, _delegatee);
}
/**
* @dev Withdraw raw tokens from the system, following an elapsed cooldown period.
* Note - May be subject to a transfer fee, depending on the users weightedTimestamp
* @param _amount Units of raw token to withdraw
* @param _recipient Address of beneficiary who will receive the raw tokens
* @param _amountIncludesFee Is the `_amount` specified inclusive of any applicable redemption fee?
* @param _exitCooldown Should we take this opportunity to exit the cooldown period?
**/
function withdraw(
uint256 _amount,
address _recipient,
bool _amountIncludesFee,
bool _exitCooldown
) external {
_withdraw(_amount, _recipient, _amountIncludesFee, _exitCooldown);
}
/**
* @dev Withdraw raw tokens from the system, following an elapsed cooldown period.
* Note - May be subject to a transfer fee, depending on the users weightedTimestamp
* @param _amount Units of raw token to withdraw
* @param _recipient Address of beneficiary who will receive the raw tokens
* @param _amountIncludesFee Is the `_amount` specified inclusive of any applicable redemption fee?
* @param _exitCooldown Should we take this opportunity to exit the cooldown period?
**/
function _withdraw(
uint256 _amount,
address _recipient,
bool _amountIncludesFee,
bool _exitCooldown
) internal assertNotContract {
require(_amount != 0, "INVALID_ZERO_AMOUNT");
// Is the contract post-recollateralisation?
if (safetyData.collateralisationRatio != 1e18) {
// 1. If recollateralisation has occured, the contract is finished and we can skip all checks
_burnRaw(_msgSender(), _amount, false, true);
// 2. Return a proportionate amount of tokens, based on the collateralisation ratio
STAKED_TOKEN.safeTransfer(
_recipient,
(_amount * safetyData.collateralisationRatio) / 1e18
);
emit Withdraw(_msgSender(), _recipient, _amount);
} else {
// 1. If no recollateralisation has occured, the user must be within their UNSTAKE_WINDOW period in order to withdraw
Balance memory oldBalance = _balances[_msgSender()];
require(
block.timestamp > oldBalance.cooldownTimestamp + COOLDOWN_SECONDS,
"INSUFFICIENT_COOLDOWN"
);
require(
block.timestamp - (oldBalance.cooldownTimestamp + COOLDOWN_SECONDS) <=
UNSTAKE_WINDOW,
"UNSTAKE_WINDOW_FINISHED"
);
// 2. Get current balance
Balance memory balance = _balances[_msgSender()];
// 3. Apply redemption fee
// e.g. (55e18 / 5e18) - 2e18 = 9e18 / 100 = 9e16
uint256 feeRate = calcRedemptionFeeRate(balance.weightedTimestamp);
// fee = amount * 1e18 / feeRate
// totalAmount = amount + fee
uint256 totalWithdraw = _amountIncludesFee
? _amount
: (_amount * (1e18 + feeRate)) / 1e18;
uint256 userWithdrawal = (totalWithdraw * 1e18) / (1e18 + feeRate);
// Check for percentage withdrawal
uint256 maxWithdrawal = oldBalance.cooldownUnits;
require(totalWithdraw <= maxWithdrawal, "Exceeds max withdrawal");
// 4. Exit cooldown if the user has specified, or if they have withdrawn everything
// Otherwise, update the percentage remaining proportionately
bool exitCooldown = _exitCooldown || totalWithdraw == maxWithdrawal;
// 5. Settle the withdrawal by burning the voting tokens
_burnRaw(_msgSender(), totalWithdraw, exitCooldown, false);
// Log any redemption fee to the rewards contract
_notifyAdditionalReward(totalWithdraw - userWithdrawal);
// Finally transfer tokens back to recipient
STAKED_TOKEN.safeTransfer(_recipient, userWithdrawal);
emit Withdraw(_msgSender(), _recipient, _amount);
}
}
/**
* @dev Enters a cooldown period, after which (and before the unstake window elapses) a user will be able
* to withdraw part or all of their staked tokens. Note, during this period, a users voting power is significantly reduced.
* If a user already has a cooldown period, then it will reset to the current block timestamp, so use wisely.
* @param _units Units of stake to cooldown for
**/
function startCooldown(uint256 _units) external {
_startCooldown(_units);
}
/**
* @dev Ends the cooldown of the sender and give them back their full voting power. This can be used to signal that
* the user no longer wishes to exit the system. Note, the cooldown can also be reset, more smoothly, as part of a stake or
* withdraw transaction.
**/
function endCooldown() external {
require(_balances[_msgSender()].cooldownTimestamp != 0, "No cooldown");
_exitCooldownPeriod(_msgSender());
emit CooldownExited(_msgSender());
}
/**
* @dev Enters a cooldown period, after which (and before the unstake window elapses) a user will be able
* to withdraw part or all of their staked tokens. Note, during this period, a users voting power is significantly reduced.
* If a user already has a cooldown period, then it will reset to the current block timestamp, so use wisely.
* @param _units Units of stake to cooldown for
**/
function _startCooldown(uint256 _units) internal {
require(balanceOf(_msgSender()) != 0, "INVALID_BALANCE_ON_COOLDOWN");
_enterCooldownPeriod(_msgSender(), _units);
emit Cooldown(_msgSender(), _units);
}
/***************************************
ADMIN
****************************************/
/**
* @dev This is a write function allowing the whitelisted recollateralisation module to slash stakers here and take
* the capital to use to recollateralise any lost value in the system. Trusting that the recollateralisation module has
* sufficient protections put in place. Note, once this has been executed, the contract is now finished, and undercollateralised,
* meaning that all users must withdraw, and will only receive a proportionate amount back relative to the colRatio.
**/
function emergencyRecollateralisation()
external
onlyRecollateralisationModule
onlyBeforeRecollateralisation
{
// 1. Change collateralisation rate
safetyData.collateralisationRatio = 1e18 - safetyData.slashingPercentage;
// 2. Take slashing percentage
uint256 balance = STAKED_TOKEN.balanceOf(address(this));
STAKED_TOKEN.safeTransfer(
_recollateraliser(),
(balance * safetyData.slashingPercentage) / 1e18
);
// 3. No functions should work anymore because the colRatio has changed
emit Recollateralised();
}
/**
* @dev Governance can change the slashing percentage here (initially 0). This is the amount of a stakers capital that is at
* risk in the recollateralisation process.
* @param _newRate Rate, where 50% == 5e17
**/
function changeSlashingPercentage(uint256 _newRate)
external
onlyGovernor
onlyBeforeRecollateralisation
{
require(_newRate <= 5e17, "Cannot exceed 50%");
safetyData.slashingPercentage = SafeCast.toUint128(_newRate);
emit SlashRateChanged(_newRate);
}
/**
* @dev Allows governance to whitelist a smart contract to interact with the StakedToken (for example a yield aggregator or simply
* a Gnosis SAFE or other)
* @param _wrapper Address of the smart contract to list
**/
function whitelistWrapper(address _wrapper) external onlyGovernor {
whitelistedWrappers[_wrapper] = true;
emit WrapperWhitelisted(_wrapper);
}
/**
* @dev Allows governance to blacklist a smart contract to end it's interaction with the StakedToken
* @param _wrapper Address of the smart contract to blacklist
**/
function blackListWrapper(address _wrapper) external onlyGovernor {
whitelistedWrappers[_wrapper] = false;
emit WrapperBlacklisted(_wrapper);
}
/***************************************
BACKWARDS COMPATIBILITY
****************************************/
/**
* @dev Allows for backwards compatibility with createLock fn, giving basic args to stake
* @param _value Units to stake
**/
function createLock(
uint256 _value,
uint256 /* _unlockTime */
) external {
_transferAndStake(_value, address(0), false);
}
/**
* @dev Allows for backwards compatibility with increaseLockAmount fn by simply staking more
* @param _value Units to stake
**/
function increaseLockAmount(uint256 _value) external {
require(balanceOf(_msgSender()) != 0, "Nothing to increase");
_transferAndStake(_value, address(0), false);
}
/**
* @dev Backwards compatibility. Previously a lock would run out and a user would call this. Now, it will take 2 calls
* to exit in order to leave. The first will initiate the cooldown period, and the second will execute a full withdrawal.
**/
function exit() external virtual {
// Since there is no immediate exit here, this can be called twice
// If there is no cooldown, or the cooldown has passed the unstake window, enter cooldown
uint128 ts = _balances[_msgSender()].cooldownTimestamp;
if (ts == 0 || block.timestamp > ts + COOLDOWN_SECONDS + UNSTAKE_WINDOW) {
(uint256 raw, uint256 cooldownUnits) = rawBalanceOf(_msgSender());
_startCooldown(raw + cooldownUnits);
}
// Else withdraw all available
else {
_withdraw(_balances[_msgSender()].cooldownUnits, _msgSender(), true, false);
}
}
/***************************************
GETTERS
****************************************/
/**
* @dev fee = sqrt(300/x)-2.5, where x = weeks since user has staked
* @param _weightedTimestamp The users weightedTimestamp
* @return _feeRate where 1% == 1e16
*/
function calcRedemptionFeeRate(uint32 _weightedTimestamp)
public
view
returns (uint256 _feeRate)
{
uint256 weeksStaked = ((block.timestamp - _weightedTimestamp) * 1e18) / ONE_WEEK;
if (weeksStaked > 3e18) {
// e.g. weeks = 1 = sqrt(300e18) = 17320508075
// e.g. weeks = 10 = sqrt(30e18) = 5477225575
// e.g. weeks = 26 = sqrt(11.5) = 3391164991
_feeRate = Root.sqrt(300e36 / weeksStaked) * 1e7;
// e.g. weeks = 1 = 173e15 - 25e15 = 148e15 or 14.8%
// e.g. weeks = 10 = 55e15 - 25e15 = 30e15 or 3%
// e.g. weeks = 26 = 34e15 - 25e15 = 9e15 or 0.9%
_feeRate = _feeRate < 25e15 ? 0 : _feeRate - 25e15;
} else {
_feeRate = 75e15;
}
}
uint256[48] private __gap;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../deps/GamifiedTokenStructs.sol";
interface IStakedToken {
// GETTERS
function COOLDOWN_SECONDS() external view returns (uint256);
function UNSTAKE_WINDOW() external view returns (uint256);
function STAKED_TOKEN() external view returns (IERC20);
function getRewardToken() external view returns (address);
function pendingAdditionalReward() external view returns (uint256);
function whitelistedWrappers(address) external view returns (bool);
function balanceData(address _account) external view returns (Balance memory);
function balanceOf(address _account) external view returns (uint256);
function rawBalanceOf(address _account) external view returns (uint256, uint256);
function calcRedemptionFeeRate(uint32 _weightedTimestamp)
external
view
returns (uint256 _feeRate);
function safetyData()
external
view
returns (uint128 collateralisationRatio, uint128 slashingPercentage);
function delegates(address account) external view returns (address);
function getPastTotalSupply(uint256 blockNumber) external view returns (uint256);
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256);
function getVotes(address account) external view returns (uint256);
// HOOKS/PERMISSIONED
function applyQuestMultiplier(address _account, uint8 _newMultiplier) external;
// ADMIN
function whitelistWrapper(address _wrapper) external;
function blackListWrapper(address _wrapper) external;
function changeSlashingPercentage(uint256 _newRate) external;
function emergencyRecollateralisation() external;
function setGovernanceHook(address _newHook) external;
// USER
function stake(uint256 _amount) external;
function stake(uint256 _amount, address _delegatee) external;
function stake(uint256 _amount, bool _exitCooldown) external;
function withdraw(
uint256 _amount,
address _recipient,
bool _amountIncludesFee,
bool _exitCooldown
) external;
function delegate(address delegatee) external;
function startCooldown(uint256 _units) external;
function endCooldown() external;
function reviewTimestamp(address _account) external;
function claimReward() external;
function claimReward(address _to) external;
// Backwards compatibility
function createLock(uint256 _value, uint256) external;
function exit() external;
function increaseLockAmount(uint256 _value) external;
function increaseLockLength(uint256) external;
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { ECDSAUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
import { GamifiedToken } from "./GamifiedToken.sol";
import { IGovernanceHook } from "./interfaces/IGovernanceHook.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @title GamifiedVotingToken
* @notice GamifiedToken is a checkpointed Voting Token derived from OpenZeppelin "ERC20VotesUpgradable"
* @author mStable
* @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/f9cdbd7d82d45a614ee98a5dc8c08fb4347d0fea/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol
* Changes:
* - Inherits custom GamifiedToken rather than basic ERC20
* - Removal of `Permit` functionality & `delegatebySig`
* - Override `delegates` fn as described in their docs
* - Prettier formatting
* - Addition of `totalSupply` method to get latest totalSupply
* - Move totalSupply checkpoints to `afterTokenTransfer`
* - Add _governanceHook hook
*/
abstract contract GamifiedVotingToken is Initializable, GamifiedToken {
struct Checkpoint {
uint32 fromBlock;
uint224 votes;
}
mapping(address => address) private _delegates;
mapping(address => Checkpoint[]) private _checkpoints;
Checkpoint[] private _totalSupplyCheckpoints;
IGovernanceHook private _governanceHook;
event GovernanceHookChanged(address indexed hook);
/**
* @dev Emitted when an account changes their delegate.
*/
event DelegateChanged(
address indexed delegator,
address indexed fromDelegate,
address indexed toDelegate
);
/**
* @dev Emitted when a token transfer or delegate change results in changes to an account's voting power.
*/
event DelegateVotesChanged(
address indexed delegate,
uint256 previousBalance,
uint256 newBalance
);
constructor(
address _nexus,
address _rewardsToken,
address _questManager,
bool _hasPriceCoeff
) GamifiedToken(_nexus, _rewardsToken, _questManager, _hasPriceCoeff) {}
function __GamifiedVotingToken_init() internal initializer {}
/**
* @dev
*/
function setGovernanceHook(address _newHook) external onlyGovernor {
_governanceHook = IGovernanceHook(_newHook);
emit GovernanceHookChanged(_newHook);
}
/**
* @dev Get the `pos`-th checkpoint for `account`.
*/
function checkpoints(address account, uint32 pos)
public
view
virtual
returns (Checkpoint memory)
{
return _checkpoints[account][pos];
}
/**
* @dev Get number of checkpoints for `account`.
*/
function numCheckpoints(address account) public view virtual returns (uint32) {
return SafeCast.toUint32(_checkpoints[account].length);
}
/**
* @dev Get the address `account` is currently delegating to.
*/
function delegates(address account) public view virtual returns (address) {
// Override as per https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol#L23
// return _delegates[account];
address delegatee = _delegates[account];
return delegatee == address(0) ? account : delegatee;
}
/**
* @dev Gets the current votes balance for `account`
*/
function getVotes(address account) public view returns (uint256) {
uint256 pos = _checkpoints[account].length;
return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
}
/**
* @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
return _checkpointsLookup(_checkpoints[account], blockNumber);
}
/**
* @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
* It is but NOT the sum of all the delegated votes!
*
* Requirements:
*
* - `blockNumber` must have been already mined
*/
function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
require(blockNumber < block.number, "ERC20Votes: block not yet mined");
return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
}
/**
* @dev Total sum of all scaled balances
*/
function totalSupply() public view override returns (uint256) {
uint256 len = _totalSupplyCheckpoints.length;
if (len == 0) return 0;
return _totalSupplyCheckpoints[len - 1].votes;
}
/**
* @dev Lookup a value in a list of (sorted) checkpoints.
*/
function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber)
private
view
returns (uint256)
{
// We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
//
// 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 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
// past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
// the same.
uint256 high = ckpts.length;
uint256 low = 0;
while (low < high) {
uint256 mid = MathUpgradeable.average(low, high);
if (ckpts[mid].fromBlock > blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}
return high == 0 ? 0 : ckpts[high - 1].votes;
}
/**
* @dev Delegate votes from the sender to `delegatee`.
*/
function delegate(address delegatee) public virtual {
return _delegate(_msgSender(), delegatee);
}
/**
* @dev Move voting power when tokens are transferred.
*
* Emits a {DelegateVotesChanged} event.
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override {
super._afterTokenTransfer(from, to, amount);
// mint or burn, update total supply
if (from == address(0) || to == address(0)) {
_writeCheckpoint(_totalSupplyCheckpoints, to == address(0) ? _subtract : _add, amount);
}
_moveVotingPower(delegates(from), delegates(to), amount);
}
/**
* @dev Change delegation for `delegator` to `delegatee`.
*
* Emits events {DelegateChanged} and {DelegateVotesChanged}.
*/
function _delegate(address delegator, address delegatee) internal virtual {
address currentDelegate = delegates(delegator);
uint256 delegatorBalance = balanceOf(delegator);
_delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
_moveVotingPower(currentDelegate, delegatee, delegatorBalance);
}
function _moveVotingPower(
address src,
address dst,
uint256 amount
) private {
if (src != dst && amount > 0) {
if (src != address(0)) {
(uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
_checkpoints[src],
_subtract,
amount
);
emit DelegateVotesChanged(src, oldWeight, newWeight);
}
if (dst != address(0)) {
(uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
_checkpoints[dst],
_add,
amount
);
emit DelegateVotesChanged(dst, oldWeight, newWeight);
}
if (address(_governanceHook) != address(0)) {
_governanceHook.moveVotingPowerHook(src, dst, amount);
}
}
}
function _writeCheckpoint(
Checkpoint[] storage ckpts,
function(uint256, uint256) view returns (uint256) op,
uint256 delta
) private returns (uint256 oldWeight, uint256 newWeight) {
uint256 pos = ckpts.length;
oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
newWeight = op(oldWeight, delta);
if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
ckpts[pos - 1].votes = SafeCast.toUint224(newWeight);
} else {
ckpts.push(
Checkpoint({
fromBlock: SafeCast.toUint32(block.number),
votes: SafeCast.toUint224(newWeight)
})
);
}
}
function _add(uint256 a, uint256 b) private pure returns (uint256) {
return a + b;
}
function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
return a - b;
}
uint256[46] private __gap;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
library Root {
/**
* @dev Returns the square root of a given number
* @param x Input
* @return y Square root of Input
*/
function sqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
else {
uint256 xx = x;
uint256 r = 1;
if (xx >= 0x100000000000000000000000000000000) {
xx >>= 128;
r <<= 64;
}
if (xx >= 0x10000000000000000) {
xx >>= 64;
r <<= 32;
}
if (xx >= 0x100000000) {
xx >>= 32;
r <<= 16;
}
if (xx >= 0x10000) {
xx >>= 16;
r <<= 8;
}
if (xx >= 0x100) {
xx >>= 8;
r <<= 4;
}
if (xx >= 0x10) {
xx >>= 4;
r <<= 2;
}
if (xx >= 0x8) {
r <<= 1;
}
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1;
r = (r + x / r) >> 1; // Seven iterations should be enough
uint256 r1 = x / r;
return uint256(r < r1 ? r : r1);
}
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*
* _Since v2.5.0:_ this module is now much more gas efficient, given net gas
* metering changes introduced in the Istanbul hardfork.
*/
contract InitializableReentrancyGuard {
bool private _notEntered;
function _initializeReentrancyGuard() internal {
// Storing an initial non-zero value makes deployment a bit more
// expensive, but in exchange the refund on every call to nonReentrant
// will be lower in amount. Since refunds are capped to a percetange of
// the total transaction's gas, it is best to keep them low in cases
// like this one, to increase the likelihood of the full refund coming
// into effect.
_notEntered = true;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
struct Balance {
/// units of staking token that has been deposited and consequently wrapped
uint88 raw;
/// (block.timestamp - weightedTimestamp) represents the seconds a user has had their full raw balance wrapped.
/// If they deposit or withdraw, the weightedTimestamp is dragged towards block.timestamp proportionately
uint32 weightedTimestamp;
/// multiplier awarded for staking for a long time
uint8 timeMultiplier;
/// multiplier duplicated from QuestManager
uint8 questMultiplier;
/// Time at which the relative cooldown began
uint32 cooldownTimestamp;
/// Units up for cooldown
uint88 cooldownUnits;
}
struct QuestBalance {
/// last timestamp at which the user made a write action to this contract
uint32 lastAction;
/// permanent multiplier applied to an account, awarded for PERMANENT QuestTypes
uint8 permMultiplier;
/// multiplier that decays after each "season" (~9 months) by 75%, to avoid multipliers getting out of control
uint8 seasonMultiplier;
}
/// @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;
/// Multiplier, from 1 == 1.01x to 100 == 2.00x
uint8 multiplier;
/// Is the current quest valid?
QuestStatus status;
/// Expiry date in seconds for the quest
uint32 expiry;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library MathUpgradeable {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow, so we distribute.
return (a / 2) + (b / 2) + (((a % 2) + (b % 2)) / 2);
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a / b + (a % b == 0 ? 0 : 1);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSAUpgradeable {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Check the signature length
// - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return recover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
}
return recover(hash, r, vs);
} else {
revert("ECDSA: invalid signature length");
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
bytes32 s;
uint8 v;
assembly {
s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
v := add(shr(255, vs), 27)
}
return recover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`, `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
require(
uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
"ECDSA: invalid signature 's' value"
);
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { SafeCastExtended } from "../../shared/SafeCastExtended.sol";
import { ILockedERC20 } from "./interfaces/ILockedERC20.sol";
import { HeadlessStakingRewards } from "../../rewards/staking/HeadlessStakingRewards.sol";
import { QuestManager } from "./QuestManager.sol";
import "./deps/GamifiedTokenStructs.sol";
/**
* @title GamifiedToken
* @notice GamifiedToken is a non-transferrable ERC20 token that has both a raw balance and a scaled balance.
* Scaled balance is determined by quests a user completes, and the length of time they keep the raw balance wrapped.
* QuestMasters can add new quests for stakers to complete, for which they are rewarded with permanent or seasonal multipliers.
* @author mStable
* @dev Originally forked from openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
* Changes:
* - Removed the transfer, transferFrom, approve fns to make non-transferrable
* - Removed `_allowances` storage
* - Removed `_beforeTokenTransfer` hook
* - Replaced standard uint256 balance with a single struct containing all data from which the scaledBalance can be derived
* - Quest system implemented that tracks a users quest status and applies multipliers for them
**/
abstract contract GamifiedToken is
ILockedERC20,
Initializable,
ContextUpgradeable,
HeadlessStakingRewards
{
/// @notice name of this token (ERC20)
bytes32 private _name;
/// @notice symbol of this token (ERC20)
bytes32 private _symbol;
/// @notice number of decimals of this token (ERC20)
uint8 public constant override decimals = 18;
/// @notice User balance structs containing all data needed to scale balance
mapping(address => Balance) internal _balances;
/// @notice Most recent price coefficients per user
mapping(address => uint256) internal _userPriceCoeff;
/// @notice Quest Manager
QuestManager public immutable questManager;
/// @notice Has variable price
bool public immutable hasPriceCoeff;
/***************************************
INIT
****************************************/
/**
* @param _nexus System nexus
* @param _rewardsToken Token that is being distributed as a reward. eg MTA
* @param _questManager Centralised manager of quests
* @param _hasPriceCoeff true if raw staked amount is multiplied by price coeff to get staked amount. eg BPT Staked Token
*/
constructor(
address _nexus,
address _rewardsToken,
address _questManager,
bool _hasPriceCoeff
) HeadlessStakingRewards(_nexus, _rewardsToken) {
questManager = QuestManager(_questManager);
hasPriceCoeff = _hasPriceCoeff;
}
/**
* @param _nameArg Token name
* @param _symbolArg Token symbol
* @param _rewardsDistributorArg mStable Rewards Distributor
*/
function __GamifiedToken_init(
bytes32 _nameArg,
bytes32 _symbolArg,
address _rewardsDistributorArg
) internal initializer {
__Context_init_unchained();
_name = _nameArg;
_symbol = _symbolArg;
HeadlessStakingRewards._initialize(_rewardsDistributorArg);
}
/**
* @dev Checks that _msgSender is the quest Manager
*/
modifier onlyQuestManager() {
require(_msgSender() == address(questManager), "Not verified");
_;
}
/***************************************
VIEWS
****************************************/
function name() public view override returns (string memory) {
return bytes32ToString(_name);
}
function symbol() public view override returns (string memory) {
return bytes32ToString(_symbol);
}
/**
* @dev Total sum of all scaled balances
* In this instance, leave to the child token.
*/
function totalSupply()
public
view
virtual
override(HeadlessStakingRewards, ILockedERC20)
returns (uint256);
/**
* @dev Simply gets scaled balance
* @return scaled balance for user
*/
function balanceOf(address _account)
public
view
virtual
override(HeadlessStakingRewards, ILockedERC20)
returns (uint256)
{
return _getBalance(_account, _balances[_account]);
}
/**
* @dev Simply gets raw balance
* @return raw balance for user
*/
function rawBalanceOf(address _account) public view returns (uint256, uint256) {
return (_balances[_account].raw, _balances[_account].cooldownUnits);
}
/**
* @dev Scales the balance of a given user by applying multipliers
*/
function _getBalance(address _account, Balance memory _balance)
internal
view
returns (uint256 balance)
{
// e.g. raw = 1000, questMultiplier = 40, timeMultiplier = 30. Cooldown of 60%
// e.g. 1000 * (100 + 40) / 100 = 1400
balance = (_balance.raw * (100 + _balance.questMultiplier)) / 100;
// e.g. 1400 * (100 + 30) / 100 = 1820
balance = (balance * (100 + _balance.timeMultiplier)) / 100;
if (hasPriceCoeff) {
// e.g. 1820 * 16000 / 10000 = 2912
balance = (balance * _userPriceCoeff[_account]) / 10000;
}
}
/**
* @notice Raw staked balance without any multipliers
*/
function balanceData(address _account) external view returns (Balance memory) {
return _balances[_account];
}
/**
* @notice Raw staked balance without any multipliers
*/
function userPriceCoeff(address _account) external view returns (uint256) {
return _userPriceCoeff[_account];
}
/***************************************
QUESTS
****************************************/
/**
* @dev Called by anyone to poke the timestamp of a given account. This allows users to
* effectively 'claim' any new timeMultiplier, but will revert if there is no change there.
*/
function reviewTimestamp(address _account) external {
_reviewWeightedTimestamp(_account);
}
/**
* @dev Adds the multiplier awarded from quest completion to a users data, taking the opportunity
* to check time multipliers etc.
* @param _account Address of user that should be updated
* @param _newMultiplier New Quest Multiplier
*/
function applyQuestMultiplier(address _account, uint8 _newMultiplier)
external
onlyQuestManager
{
require(_account != address(0), "Invalid address");
// 1. Get current balance & update questMultiplier, only if user has a balance
Balance memory oldBalance = _balances[_account];
uint256 oldScaledBalance = _getBalance(_account, oldBalance);
if (oldScaledBalance > 0) {
_applyQuestMultiplier(_account, oldBalance, oldScaledBalance, _newMultiplier);
}
}
/**
* @dev Gets the multiplier awarded for a given weightedTimestamp
* @param _ts WeightedTimestamp of a user
* @return timeMultiplier Ranging from 20 (0.2x) to 60 (0.6x)
*/
function _timeMultiplier(uint32 _ts) internal view returns (uint8 timeMultiplier) {
// If the user has no ts yet, they are not in the system
if (_ts == 0) return 0;
uint256 hodlLength = block.timestamp - _ts;
if (hodlLength < 13 weeks) {
// 0-3 months = 1x
return 0;
} else if (hodlLength < 26 weeks) {
// 3 months = 1.2x
return 20;
} else if (hodlLength < 52 weeks) {
// 6 months = 1.3x
return 30;
} else if (hodlLength < 78 weeks) {
// 12 months = 1.4x
return 40;
} else if (hodlLength < 104 weeks) {
// 18 months = 1.5x
return 50;
} else {
// > 24 months = 1.6x
return 60;
}
}
function _getPriceCoeff() internal virtual returns (uint256) {
return 10000;
}
/***************************************
BALANCE CHANGES
****************************************/
/**
* @dev Adds the multiplier awarded from quest completion to a users data, taking the opportunity
* to check time multiplier.
* @param _account Address of user that should be updated
* @param _newMultiplier New Quest Multiplier
*/
function _applyQuestMultiplier(
address _account,
Balance memory _oldBalance,
uint256 _oldScaledBalance,
uint8 _newMultiplier
) private updateReward(_account) {
// 1. Set the questMultiplier
_balances[_account].questMultiplier = _newMultiplier;
// 2. Take the opportunity to set weighted timestamp, if it changes
_balances[_account].timeMultiplier = _timeMultiplier(_oldBalance.weightedTimestamp);
// 3. Update scaled balance
_settleScaledBalance(_account, _oldScaledBalance);
}
/**
* @dev Entering a cooldown period means a user wishes to withdraw. With this in mind, their balance
* should be reduced until they have shown more commitment to the system
* @param _account Address of user that should be cooled
* @param _units Units to cooldown for
*/
function _enterCooldownPeriod(address _account, uint256 _units)
internal
updateReward(_account)
{
require(_account != address(0), "Invalid address");
// 1. Get current balance
(Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
uint88 totalUnits = oldBalance.raw + oldBalance.cooldownUnits;
require(_units > 0 && _units <= totalUnits, "Must choose between 0 and 100%");
// 2. Set weighted timestamp and enter cooldown
_balances[_account].timeMultiplier = _timeMultiplier(oldBalance.weightedTimestamp);
// e.g. 1e18 / 1e16 = 100, 2e16 / 1e16 = 2, 1e15/1e16 = 0
_balances[_account].raw = totalUnits - SafeCastExtended.toUint88(_units);
// 3. Set cooldown data
_balances[_account].cooldownTimestamp = SafeCastExtended.toUint32(block.timestamp);
_balances[_account].cooldownUnits = SafeCastExtended.toUint88(_units);
// 4. Update scaled balance
_settleScaledBalance(_account, oldScaledBalance);
}
/**
* @dev Exiting the cooldown period explicitly resets the users cooldown window and their balance
* @param _account Address of user that should be exited
*/
function _exitCooldownPeriod(address _account) internal updateReward(_account) {
require(_account != address(0), "Invalid address");
// 1. Get current balance
(Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
// 2. Set weighted timestamp and exit cooldown
_balances[_account].timeMultiplier = _timeMultiplier(oldBalance.weightedTimestamp);
_balances[_account].raw += oldBalance.cooldownUnits;
// 3. Set cooldown data
_balances[_account].cooldownTimestamp = 0;
_balances[_account].cooldownUnits = 0;
// 4. Update scaled balance
_settleScaledBalance(_account, oldScaledBalance);
}
/**
* @dev Pokes the weightedTimestamp of a given user and checks if it entitles them
* to a better timeMultiplier. If not, it simply reverts as there is nothing to update.
* @param _account Address of user that should be updated
*/
function _reviewWeightedTimestamp(address _account) internal updateReward(_account) {
require(_account != address(0), "Invalid address");
// 1. Get current balance
(Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
// 2. Set weighted timestamp, if it changes
uint8 newTimeMultiplier = _timeMultiplier(oldBalance.weightedTimestamp);
require(newTimeMultiplier != oldBalance.timeMultiplier, "Nothing worth poking here");
_balances[_account].timeMultiplier = newTimeMultiplier;
// 3. Update scaled balance
_settleScaledBalance(_account, oldScaledBalance);
}
/**
* @dev Called to mint from raw tokens. Adds raw to a users balance, and then propagates the scaledBalance.
* 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 Should we end any cooldown?
*/
function _mintRaw(
address _account,
uint256 _rawAmount,
bool _exitCooldown
) internal updateReward(_account) {
require(_account != address(0), "ERC20: mint to the zero address");
// 1. Get and update current balance
(Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
uint88 totalRaw = oldBalance.raw + oldBalance.cooldownUnits;
_balances[_account].raw = oldBalance.raw + SafeCastExtended.toUint88(_rawAmount);
// 2. Exit cooldown if necessary
if (_exitCooldown) {
_balances[_account].raw += oldBalance.cooldownUnits;
_balances[_account].cooldownTimestamp = 0;
_balances[_account].cooldownUnits = 0;
}
// 3. Set weighted timestamp
// i) For new _account, set up weighted timestamp
if (oldBalance.weightedTimestamp == 0) {
_balances[_account].weightedTimestamp = SafeCastExtended.toUint32(block.timestamp);
_mintScaled(_account, _getBalance(_account, _balances[_account]));
return;
}
// ii) For previous minters, recalculate time held
// Calc new weighted timestamp
uint256 oldWeightedSecondsHeld = (block.timestamp - oldBalance.weightedTimestamp) *
totalRaw;
uint256 newSecondsHeld = oldWeightedSecondsHeld / (totalRaw + (_rawAmount / 2));
uint32 newWeightedTs = SafeCastExtended.toUint32(block.timestamp - newSecondsHeld);
_balances[_account].weightedTimestamp = newWeightedTs;
uint8 timeMultiplier = _timeMultiplier(newWeightedTs);
_balances[_account].timeMultiplier = timeMultiplier;
// 3. Update scaled balance
_settleScaledBalance(_account, oldScaledBalance);
}
/**
* @dev Called to burn a given amount of raw tokens.
* @param _account Address of user
* @param _rawAmount Raw amount of tokens to remove
* @param _exitCooldown Exit the cooldown?
* @param _finalise Has recollateralisation happened? If so, everything is cooled down
*/
function _burnRaw(
address _account,
uint256 _rawAmount,
bool _exitCooldown,
bool _finalise
) internal updateReward(_account) {
require(_account != address(0), "ERC20: burn from zero address");
// 1. Get and update current balance
(Balance memory oldBalance, uint256 oldScaledBalance) = _prepareOldBalance(_account);
uint256 totalRaw = oldBalance.raw + oldBalance.cooldownUnits;
// 1.1. If _finalise, move everything to cooldown
if (_finalise) {
_balances[_account].raw = 0;
_balances[_account].cooldownUnits = SafeCastExtended.toUint88(totalRaw);
oldBalance.cooldownUnits = SafeCastExtended.toUint88(totalRaw);
}
// 1.2. Update
require(oldBalance.cooldownUnits >= _rawAmount, "ERC20: burn amount > balance");
unchecked {
_balances[_account].cooldownUnits -= SafeCastExtended.toUint88(_rawAmount);
}
// 2. If we are exiting cooldown, reset the balance
if (_exitCooldown) {
_balances[_account].raw += _balances[_account].cooldownUnits;
_balances[_account].cooldownTimestamp = 0;
_balances[_account].cooldownUnits = 0;
}
// 3. Set back scaled time
// e.g. stake 10 for 100 seconds, withdraw 5.
// secondsHeld = (100 - 0) * (10 - 0.625) = 937.5
uint256 secondsHeld = (block.timestamp - oldBalance.weightedTimestamp) *
(totalRaw - (_rawAmount / 8));
// newWeightedTs = 937.5 / 100 = 93.75
uint256 newSecondsHeld = secondsHeld / totalRaw;
uint32 newWeightedTs = SafeCastExtended.toUint32(block.timestamp - newSecondsHeld);
_balances[_account].weightedTimestamp = newWeightedTs;
uint8 timeMultiplier = _timeMultiplier(newWeightedTs);
_balances[_account].timeMultiplier = timeMultiplier;
// 4. Update scaled balance
_settleScaledBalance(_account, oldScaledBalance);
}
/***************************************
PRIVATE
updateReward should already be called by now
****************************************/
/**
* @dev Fetches the balance of a given user, scales it, and also takes the opportunity
* to check if the season has just finished between now and their last action.
* @param _account Address of user to fetch
* @return oldBalance struct containing all balance information
* @return oldScaledBalance scaled balance after applying multipliers
*/
function _prepareOldBalance(address _account)
private
returns (Balance memory oldBalance, uint256 oldScaledBalance)
{
// Get the old balance
oldBalance = _balances[_account];
oldScaledBalance = _getBalance(_account, oldBalance);
// Take the opportunity to check for season finish
_balances[_account].questMultiplier = questManager.checkForSeasonFinish(_account);
if (hasPriceCoeff) {
_userPriceCoeff[_account] = SafeCastExtended.toUint16(_getPriceCoeff());
}
}
/**
* @dev Settles the scaled balance of a given account. The reason this is done here, is because
* in each of the write functions above, there is the chance that a users balance can go down,
* requiring to burn sacled tokens. This could happen at the end of a season when multipliers are slashed.
* This is called after updating all multipliers etc.
* @param _account Address of user that should be updated
* @param _oldScaledBalance Previous scaled balance of the user
*/
function _settleScaledBalance(address _account, uint256 _oldScaledBalance) private {
uint256 newScaledBalance = _getBalance(_account, _balances[_account]);
if (newScaledBalance > _oldScaledBalance) {
_mintScaled(_account, newScaledBalance - _oldScaledBalance);
}
// This can happen if the user moves back a time class, but is unlikely to result in a negative mint
else {
_burnScaled(_account, _oldScaledBalance - newScaledBalance);
}
}
/**
* @dev Propagates the minting of the tokens downwards.
* @param _account Address of user that has minted
* @param _amount Amount of scaled tokens minted
*/
function _mintScaled(address _account, uint256 _amount) private {
emit Transfer(address(0), _account, _amount);
_afterTokenTransfer(address(0), _account, _amount);
}
/**
* @dev Propagates the burning of the tokens downwards.
* @param _account Address of user that has burned
* @param _amount Amount of scaled tokens burned
*/
function _burnScaled(address _account, uint256 _amount) private {
emit Transfer(_account, address(0), _amount);
_afterTokenTransfer(_account, address(0), _amount);
}
/***************************************
HOOKS
****************************************/
/**
* @dev Triggered after a user claims rewards from the HeadlessStakingRewards. Used
* to check for season finish. If it has not, then do not spend gas updating the other vars.
* @param _account Address of user that has burned
*/
function _claimRewardHook(address _account) internal override {
uint8 newMultiplier = questManager.checkForSeasonFinish(_account);
bool priceCoeffChanged = hasPriceCoeff
? _getPriceCoeff() != _userPriceCoeff[_account]
: false;
if (newMultiplier != _balances[_account].questMultiplier || priceCoeffChanged) {
// 1. Get current balance & trigger season finish
uint256 oldScaledBalance = _getBalance(_account, _balances[_account]);
_balances[_account].questMultiplier = newMultiplier;
if (priceCoeffChanged) {
_userPriceCoeff[_account] = SafeCastExtended.toUint16(_getPriceCoeff());
}
// 3. Update scaled balance
_settleScaledBalance(_account, oldScaledBalance);
}
}
/**
* @dev Unchanged from OpenZeppelin. Used in child contracts to react to any balance changes.
*/
function _afterTokenTransfer(
address _from,
address _to,
uint256 _amount
) internal virtual {}
/***************************************
Utils
****************************************/
function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
uint256 i = 0;
while (i < 32 && _bytes32[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
bytesArray[i] = _bytes32[i];
}
return string(bytesArray);
}
uint256[46] private __gap;
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
interface IGovernanceHook {
function moveVotingPowerHook(
address from,
address to,
uint256 amount
) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
require(_initializing || !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal initializer {
__Context_init_unchained();
}
function __Context_init_unchained() internal initializer {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
uint256[50] private __gap;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCastExtended {
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toUint88(uint256 value) internal pure returns (uint88) {
require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
return uint88(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(
value >= type(int128).min && value <= type(int128).max,
"SafeCast: value doesn't fit in 128 bits"
);
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(
value >= type(int64).min && value <= type(int64).max,
"SafeCast: value doesn't fit in 64 bits"
);
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(
value >= type(int32).min && value <= type(int32).max,
"SafeCast: value doesn't fit in 32 bits"
);
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(
value >= type(int16).min && value <= type(int16).max,
"SafeCast: value doesn't fit in 16 bits"
);
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(
value >= type(int8).min && value <= type(int8).max,
"SafeCast: value doesn't fit in 8 bits"
);
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface ILockedERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
// Internal
import { InitializableRewardsDistributionRecipient } from "../InitializableRewardsDistributionRecipient.sol";
import { StableMath } from "../../shared/StableMath.sol";
import { PlatformTokenVendorFactory } from "./PlatformTokenVendorFactory.sol";
import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
// Libs
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @title HeadlessStakingRewards
* @author mStable
* @notice Rewards stakers of a given LP token with REWARDS_TOKEN, on a pro-rata basis
* @dev Forked from `StakingRewards.sol`
* Changes:
* - `pendingAdditionalReward` added to support accumulation of any extra staking token
* - Removal of `StakingTokenWrapper`, instead, deposits and withdrawals are made in child contract,
* and balances are read from there through the abstract functions
*/
abstract contract HeadlessStakingRewards is
ContextUpgradeable,
InitializableRewardsDistributionRecipient
{
using SafeERC20 for IERC20;
using StableMath for uint256;
/// @notice token the rewards are distributed in. eg MTA
IERC20 public immutable REWARDS_TOKEN;
/// @notice length of each staking period in seconds. 7 days = 604,800; 3 months = 7,862,400
uint256 public constant DURATION = 1 weeks;
/// @notice contract that holds the platform tokens
address public rewardTokenVendor;
struct Data {
/// Timestamp for current period finish
uint32 periodFinish;
/// Last time any user took action
uint32 lastUpdateTime;
/// RewardRate for the rest of the period
uint96 rewardRate;
/// Ever increasing rewardPerToken rate, based on % of total supply
uint96 rewardPerTokenStored;
}
struct UserData {
uint128 rewardPerTokenPaid;
uint128 rewards;
}
Data public globalData;
mapping(address => UserData) public userData;
uint256 public pendingAdditionalReward;
event RewardAdded(uint256 reward);
event RewardPaid(address indexed user, address indexed to, uint256 reward);
/**
* @param _nexus mStable system Nexus address
* @param _rewardsToken first token that is being distributed as a reward. eg MTA
*/
constructor(address _nexus, address _rewardsToken)
InitializableRewardsDistributionRecipient(_nexus)
{
REWARDS_TOKEN = IERC20(_rewardsToken);
}
/**
* @dev Initialization function for upgradable proxy contract.
* This function should be called via Proxy just after contract deployment.
* To avoid variable shadowing appended `Arg` after arguments name.
* @param _rewardsDistributorArg mStable Reward Distributor contract address
*/
function _initialize(address _rewardsDistributorArg) internal virtual override {
InitializableRewardsDistributionRecipient._initialize(_rewardsDistributorArg);
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
if (newRewardPerToken > 0) {
globalData.rewardPerTokenStored = SafeCast.toUint96(newRewardPerToken);
globalData.lastUpdateTime = SafeCast.toUint32(lastApplicableTime);
// Setting of personal vars based on new globals
if (_account != address(0)) {
userData[_account] = UserData({
rewardPerTokenPaid: SafeCast.toUint128(newRewardPerToken),
rewards: SafeCast.toUint128(_earned(_account, newRewardPerToken))
});
}
}
}
/***************************************
ACTIONS
****************************************/
/**
* @dev Claims outstanding rewards for the sender.
* First updates outstanding reward allocation and then transfers.
*/
function claimReward(address _to) public {
_claimReward(_to);
}
/**
* @dev Claims outstanding rewards for the sender.
* First updates outstanding reward allocation and then transfers.
*/
function claimReward() public {
_claimReward(_msgSender());
}
function _claimReward(address _to) internal updateReward(_msgSender()) {
uint128 reward = userData[_msgSender()].rewards;
if (reward > 0) {
userData[_msgSender()].rewards = 0;
REWARDS_TOKEN.safeTransferFrom(rewardTokenVendor, _to, reward);
emit RewardPaid(_msgSender(), _to, reward);
}
_claimRewardHook(_msgSender());
}
/***************************************
GETTERS
****************************************/
/**
* @dev Gets the RewardsToken
*/
function getRewardToken() external view override returns (IERC20) {
return REWARDS_TOKEN;
}
/**
* @dev Gets the last applicable timestamp for this reward period
*/
function lastTimeRewardApplicable() public view returns (uint256) {
return StableMath.min(block.timestamp, globalData.periodFinish);
}
/**
* @dev Calculates the amount of unclaimed rewards per token since last update,
* and sums with stored to give the new cumulative reward per token
* @return 'Reward' per staked token
*/
function rewardPerToken() public view returns (uint256) {
(uint256 rewardPerToken_, ) = _rewardPerToken();
return rewardPerToken_;
}
function _rewardPerToken()
internal
view
returns (uint256 rewardPerToken_, uint256 lastTimeRewardApplicable_)
{
uint256 lastApplicableTime = lastTimeRewardApplicable(); // + 1 SLOAD
Data memory data = globalData;
uint256 timeDelta = lastApplicableTime - data.lastUpdateTime; // + 1 SLOAD
// If this has been called twice in the same block, shortcircuit to reduce gas
if (timeDelta == 0) {
return (data.rewardPerTokenStored, lastApplicableTime);
}
// new reward units to distribute = rewardRate * timeSinceLastUpdate
uint256 rewardUnitsToDistribute = data.rewardRate * timeDelta; // + 1 SLOAD
uint256 supply = totalSupply(); // + 1 SLOAD
// If there is no StakingToken liquidity, avoid div(0)
// If there is nothing to distribute, short circuit
if (supply == 0 || rewardUnitsToDistribute == 0) {
return (data.rewardPerTokenStored, lastApplicableTime);
}
// new reward units per token = (rewardUnitsToDistribute * 1e18) / totalTokens
uint256 unitsToDistributePerToken = rewardUnitsToDistribute.divPrecisely(supply);
// return summed rate
return (data.rewardPerTokenStored + unitsToDistributePerToken, lastApplicableTime); // + 1 SLOAD
}
/**
* @dev Calculates the amount of unclaimed rewards a user has earned
* @param _account User address
* @return Total reward amount earned
*/
function earned(address _account) public view returns (uint256) {
return _earned(_account, rewardPerToken());
}
function _earned(address _account, uint256 _currentRewardPerToken)
internal
view
returns (uint256)
{
// current rate per token - rate user previously received
uint256 userRewardDelta = _currentRewardPerToken - userData[_account].rewardPerTokenPaid; // + 1 SLOAD
// Short circuit if there is nothing new to distribute
if (userRewardDelta == 0) {
return userData[_account].rewards;
}
// new reward = staked tokens * difference in rate
uint256 userNewReward = balanceOf(_account).mulTruncate(userRewardDelta); // + 1 SLOAD
// add to previous rewards
return userData[_account].rewards + userNewReward;
}
/***************************************
ABSTRACT
****************************************/
function balanceOf(address account) public view virtual returns (uint256);
function totalSupply() public view virtual returns (uint256);
function _claimRewardHook(address account) internal virtual;
/***************************************
ADMIN
****************************************/
/**
* @dev Notifies the contract that new rewards have been added.
* Calculates an updated rewardRate based on the rewards in period.
* @param _reward Units of RewardToken that have been added to the pool
*/
function notifyRewardAmount(uint256 _reward)
external
override
onlyRewardsDistributor
updateReward(address(0))
{
require(_reward < 1e24, "Notify more than a million units");
uint256 currentTime = block.timestamp;
// Pay and reset the pendingAdditionalRewards
if (pendingAdditionalReward > 1) {
_reward += (pendingAdditionalReward - 1);
pendingAdditionalReward = 1;
}
if (_reward > 0) {
REWARDS_TOKEN.safeTransfer(rewardTokenVendor, _reward);
}
// If previous period over, reset rewardRate
if (currentTime >= globalData.periodFinish) {
globalData.rewardRate = SafeCast.toUint96(_reward / DURATION);
}
// If additional reward to existing period, calc sum
else {
uint256 remainingSeconds = globalData.periodFinish - currentTime;
uint256 leftover = remainingSeconds * globalData.rewardRate;
globalData.rewardRate = SafeCast.toUint96((_reward + leftover) / DURATION);
}
globalData.lastUpdateTime = SafeCast.toUint32(currentTime);
globalData.periodFinish = SafeCast.toUint32(currentTime + DURATION);
emit RewardAdded(_reward);
}
/**
* @dev Called by the child contract to notify of any additional rewards that have accrued.
* Trusts that this is called honestly.
* @param _additionalReward Units of additional RewardToken to add at the next notification
*/
function _notifyAdditionalReward(uint256 _additionalReward) internal virtual {
require(_additionalReward < 1e24, "Cannot notify with more than a million units");
pendingAdditionalReward += _additionalReward;
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { SignatureVerifier } from "./deps/SignatureVerifier.sol";
import { ImmutableModule } from "../../shared/ImmutableModule.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IQuestManager } from "./interfaces/IQuestManager.sol";
import { IStakedToken } from "./interfaces/IStakedToken.sol";
import "./deps/GamifiedTokenStructs.sol";
/**
* @title QuestManager
* @author mStable
* @notice Centralised place to track quest management and completion status
* @dev VERSION: 1.0
* DATE: 2021-08-25
*/
contract QuestManager is IQuestManager, Initializable, ContextUpgradeable, ImmutableModule {
/// @notice Tracks the completion of each quest (user => questId => completion)
mapping(address => mapping(uint256 => bool)) private _questCompletion;
/// @notice User balance structs containing all data needed to scale balance
mapping(address => QuestBalance) internal _balances;
/// @notice List of quests, whose ID corresponds to their position in the array (from 0)
Quest[] private _quests;
/// @notice Timestamp at which the current season started
uint32 public override seasonEpoch;
/// @notice Timestamp at which the contract was created
uint32 public startTime;
/// @notice A whitelisted questMaster who can administer quests including signing user quests are completed.
address public override questMaster;
/// @notice account that can sign a user's quest as being completed.
address internal _questSigner;
/// @notice List of all staking tokens
address[] internal _stakedTokens;
/**
* @param _nexus System nexus
*/
constructor(address _nexus) ImmutableModule(_nexus) {}
/**
* @param _questMaster account that can sign user quests as completed
* @param _questSignerArg account that can sign user quests as completed
*/
function initialize(address _questMaster, address _questSignerArg) external initializer {
startTime = SafeCast.toUint32(block.timestamp);
questMaster = _questMaster;
_questSigner = _questSignerArg;
}
/**
* @dev Checks that _msgSender is either governor or the quest master
*/
modifier questMasterOrGovernor() {
_questMasterOrGovernor();
_;
}
function _questMasterOrGovernor() internal view {
require(_msgSender() == questMaster || _msgSender() == _governor(), "Not verified");
}
/***************************************
Getters
****************************************/
/**
* @notice Gets raw quest data
*/
function getQuest(uint256 _id) external view override returns (Quest memory) {
return _quests[_id];
}
/**
* @dev Simply checks if a given user has already completed a given quest
* @param _account User address
* @param _id Position of quest in array
* @return bool with completion status
*/
function hasCompleted(address _account, uint256 _id) public view override returns (bool) {
return _questCompletion[_account][_id];
}
/**
* @notice Raw quest balance
*/
function balanceData(address _account) external view override returns (QuestBalance memory) {
return _balances[_account];
}
/***************************************
Admin
****************************************/
/**
* @dev Sets the quest master that can administoer quests. eg add, expire and start seasons.
*/
function setQuestMaster(address _newQuestMaster) external override questMasterOrGovernor {
emit QuestMaster(questMaster, _newQuestMaster);
questMaster = _newQuestMaster;
}
/**
* @dev Sets the quest signer that can sign user quests as being completed.
*/
function setQuestSigner(address _newQuestSigner) external override onlyGovernor {
emit QuestSigner(_questSigner, _newQuestSigner);
_questSigner = _newQuestSigner;
}
/**
* @dev Adds a new stakedToken
*/
function addStakedToken(address _stakedToken) external override onlyGovernor {
require(_stakedToken != address(0), "Invalid StakedToken");
_stakedTokens.push(_stakedToken);
emit StakedTokenAdded(_stakedToken);
}
/***************************************
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(
QuestType _model,
uint8 _multiplier,
uint32 _expiry
) external override 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(
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(uint16 _id) external override questMasterOrGovernor {
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() external override questMasterOrGovernor {
require(block.timestamp > (startTime + 39 weeks), "First season has not elapsed");
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"
);
}
}
seasonEpoch = SafeCast.toUint32(block.timestamp);
emit QuestSeasonEnded();
}
/***************************************
USER
****************************************/
/**
* @dev Called by anyone to complete one or more quests for a staker. The user must first collect a signed message
* from the whitelisted _signer.
* @param _account Account that has completed the quest
* @param _ids Quest IDs (its position in the array)
* @param _signature Signature from the verified _questSigner, containing keccak hash of account & ids
*/
function completeUserQuests(
address _account,
uint256[] memory _ids,
bytes calldata _signature
) external override {
uint256 len = _ids.length;
require(len > 0, "No quest IDs");
uint8 questMultiplier = checkForSeasonFinish(_account);
// For each quest
for (uint256 i = 0; i < len; i++) {
require(_validQuest(_ids[i]), "Invalid Quest ID");
require(!hasCompleted(_account, _ids[i]), "Quest already completed");
require(
SignatureVerifier.verify(_questSigner, _account, _ids, _signature),
"Invalid Quest Signer Signature"
);
// Store user quest has completed
_questCompletion[_account][_ids[i]] = true;
// Update multiplier
Quest memory quest = _quests[_ids[i]];
if (quest.model == QuestType.PERMANENT) {
_balances[_account].permMultiplier += quest.multiplier;
} else {
_balances[_account].seasonMultiplier += quest.multiplier;
}
questMultiplier += quest.multiplier;
}
uint256 len2 = _stakedTokens.length;
for (uint256 i = 0; i < len2; i++) {
IStakedToken(_stakedTokens[i]).applyQuestMultiplier(_account, questMultiplier);
}
emit QuestCompleteQuests(_account, _ids);
}
/**
* @dev Called by anyone to complete one or more accounts for a quest. The user must first collect a signed message
* from the whitelisted _questMaster.
* @param _questId Quest ID (its position in the array)
* @param _accounts Accounts that has completed the quest
* @param _signature Signature from the verified _questMaster, containing keccak hash of id and accounts
*/
function completeQuestUsers(
uint256 _questId,
address[] memory _accounts,
bytes calldata _signature
) external override {
require(_validQuest(_questId), "Invalid Quest ID");
uint256 len = _accounts.length;
require(len > 0, "No accounts");
require(
SignatureVerifier.verify(_questSigner, _questId, _accounts, _signature),
"Invalid Quest Signer Signature"
);
Quest memory quest = _quests[_questId];
// For each user account
for (uint256 i = 0; i < len; i++) {
require(!hasCompleted(_accounts[i], _questId), "Quest already completed");
// store user quest has completed
_questCompletion[_accounts[i]][_questId] = true;
// _applyQuestMultiplier(_accounts[i], quests);
uint8 questMultiplier = checkForSeasonFinish(_accounts[i]);
// Update multiplier
if (quest.model == QuestType.PERMANENT) {
_balances[_accounts[i]].permMultiplier += quest.multiplier;
} else {
_balances[_accounts[i]].seasonMultiplier += quest.multiplier;
}
questMultiplier += quest.multiplier;
uint256 len2 = _stakedTokens.length;
for (uint256 j = 0; j < len2; j++) {
IStakedToken(_stakedTokens[j]).applyQuestMultiplier(_accounts[i], questMultiplier);
}
}
emit QuestCompleteUsers(_questId, _accounts);
}
/**
* @dev Simply checks if a quest is valid. Quests are valid if their id exists,
* they have an ACTIVE status and they have not yet reached their expiry timestamp.
* @param _id Position of quest in array
* @return bool with validity status
*/
function _validQuest(uint256 _id) internal view returns (bool) {
return
_id < _quests.length &&
_quests[_id].status == QuestStatus.ACTIVE &&
block.timestamp < _quests[_id].expiry;
}
/**
* @dev Checks if the season has just finished between now and the users last action.
* If it has, we reset the seasonMultiplier. Either way, we update the lastAction for the user.
* NOTE - it is important that this is called as a hook before each state change operation
* @param _account Address of user that should be updated
*/
function checkForSeasonFinish(address _account)
public
override
returns (uint8 newQuestMultiplier)
{
QuestBalance storage balance = _balances[_account];
// If the last action was before current season, then reset the season timing
if (_hasFinishedSeason(balance.lastAction)) {
// Remove 85% of the multiplier gained in this season
balance.seasonMultiplier = (balance.seasonMultiplier * 15) / 100;
balance.lastAction = SafeCast.toUint32(block.timestamp);
}
return balance.seasonMultiplier + balance.permMultiplier;
}
/**
* @dev Simple view fn to check if the users last action was before the starting of the current season
*/
function _hasFinishedSeason(uint32 _lastAction) internal view returns (bool) {
return _lastAction < seasonEpoch;
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { ImmutableModule } from "../shared/ImmutableModule.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IRewardsDistributionRecipient } from "../interfaces/IRewardsDistributionRecipient.sol";
/**
* @title RewardsDistributionRecipient
* @author Originally: Synthetix (forked from /Synthetixio/synthetix/contracts/RewardsDistributionRecipient.sol)
* Changes by: mStable
* @notice RewardsDistributionRecipient gets notified of additional rewards by the rewardsDistributor
* @dev Changes: Addition of Module and abstract `getRewardToken` func + cosmetic
*/
abstract contract InitializableRewardsDistributionRecipient is
IRewardsDistributionRecipient,
ImmutableModule
{
// This address has the ability to distribute the rewards
address public rewardsDistributor;
constructor(address _nexus) ImmutableModule(_nexus) {}
/** @dev Recipient is a module, governed by mStable governance */
function _initialize(address _rewardsDistributor) internal virtual {
rewardsDistributor = _rewardsDistributor;
}
/**
* @dev Only the rewards distributor can notify about rewards
*/
modifier onlyRewardsDistributor() {
require(msg.sender == rewardsDistributor, "Caller is not reward distributor");
_;
}
/**
* @dev Change the rewardsDistributor - only called by mStable governor
* @param _rewardsDistributor Address of the new distributor
*/
function setRewardsDistribution(address _rewardsDistributor) external onlyGovernor {
rewardsDistributor = _rewardsDistributor;
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
/**
* @title StableMath
* @author mStable
* @notice A library providing safe mathematical operations to multiply and
* divide with standardised precision.
* @dev Derives from OpenZeppelin's SafeMath lib and uses generic system
* wide variables for managing precision.
*/
library StableMath {
/**
* @dev Scaling unit for use in specific calculations,
* where 1 * 10**18, or 1e18 represents a unit '1'
*/
uint256 private constant FULL_SCALE = 1e18;
/**
* @dev Token Ratios are used when converting between units of bAsset, mAsset and MTA
* Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
* bAsset ratio unit for use in exact calculations,
* where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
*/
uint256 private constant RATIO_SCALE = 1e8;
/**
* @dev Provides an interface to the scaling unit
* @return Scaling unit (1e18 or 1 * 10**18)
*/
function getFullScale() internal pure returns (uint256) {
return FULL_SCALE;
}
/**
* @dev Provides an interface to the ratio unit
* @return Ratio scale unit (1e8 or 1 * 10**8)
*/
function getRatioScale() internal pure returns (uint256) {
return RATIO_SCALE;
}
/**
* @dev Scales a given integer to the power of the full scale.
* @param x Simple uint256 to scale
* @return Scaled value a to an exact number
*/
function scaleInteger(uint256 x) internal pure returns (uint256) {
return x * FULL_SCALE;
}
/***************************************
PRECISE ARITHMETIC
****************************************/
/**
* @dev Multiplies two precise units, and then truncates by the full scale
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {
return mulTruncateScale(x, y, FULL_SCALE);
}
/**
* @dev Multiplies two precise units, and then truncates by the given scale. For example,
* when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @param scale Scale unit
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncateScale(
uint256 x,
uint256 y,
uint256 scale
) internal pure returns (uint256) {
// e.g. assume scale = fullScale
// z = 10e18 * 9e17 = 9e36
// return 9e36 / 1e18 = 9e18
return (x * y) / scale;
}
/**
* @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit, rounded up to the closest base unit.
*/
function mulTruncateCeil(uint256 x, uint256 y) internal pure returns (uint256) {
// e.g. 8e17 * 17268172638 = 138145381104e17
uint256 scaled = x * y;
// e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
uint256 ceil = scaled + FULL_SCALE - 1;
// e.g. 13814538111.399...e18 / 1e18 = 13814538111
return ceil / FULL_SCALE;
}
/**
* @dev Precisely divides two units, by first scaling the left hand operand. Useful
* for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
* @param x Left hand input to division
* @param y Right hand input to division
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divPrecisely(uint256 x, uint256 y) internal pure returns (uint256) {
// e.g. 8e18 * 1e18 = 8e36
// e.g. 8e36 / 10e18 = 8e17
return (x * FULL_SCALE) / y;
}
/***************************************
RATIO FUNCS
****************************************/
/**
* @dev Multiplies and truncates a token ratio, essentially flooring the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand operand to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return c Result after multiplying the two inputs and then dividing by the ratio scale
*/
function mulRatioTruncate(uint256 x, uint256 ratio) internal pure returns (uint256 c) {
return mulTruncateScale(x, ratio, RATIO_SCALE);
}
/**
* @dev Multiplies and truncates a token ratio, rounding up the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand input to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the shared
* ratio scale, rounded up to the closest base unit.
*/
function mulRatioTruncateCeil(uint256 x, uint256 ratio) internal pure returns (uint256) {
// e.g. How much mAsset should I burn for this bAsset (x)?
// 1e18 * 1e8 = 1e26
uint256 scaled = x * ratio;
// 1e26 + 9.99e7 = 100..00.999e8
uint256 ceil = scaled + RATIO_SCALE - 1;
// return 100..00.999e8 / 1e8 = 1e18
return ceil / RATIO_SCALE;
}
/**
* @dev Precisely divides two ratioed units, by first scaling the left hand operand
* i.e. How much bAsset is this mAsset worth?
* @param x Left hand operand in division
* @param ratio bAsset ratio
* @return c Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divRatioPrecisely(uint256 x, uint256 ratio) internal pure returns (uint256 c) {
// e.g. 1e14 * 1e8 = 1e22
// return 1e22 / 1e12 = 1e10
return (x * RATIO_SCALE) / ratio;
}
/***************************************
HELPERS
****************************************/
/**
* @dev Calculates minimum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Minimum of the two inputs
*/
function min(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
/**
* @dev Calculated maximum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Maximum of the two inputs
*/
function max(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? x : y;
}
/**
* @dev Clamps a value to an upper bound
* @param x Left hand input
* @param upperBound Maximum possible value to return
* @return Input x clamped to a maximum value, upperBound
*/
function clamp(uint256 x, uint256 upperBound) internal pure returns (uint256) {
return x > upperBound ? upperBound : x;
}
}// 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);
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { ModuleKeys } from "./ModuleKeys.sol";
import { INexus } from "../interfaces/INexus.sol";
/**
* @title ImmutableModule
* @author mStable
* @dev Subscribes to module updates from a given publisher and reads from its registry.
* Contract is used for upgradable proxy contracts.
*/
abstract contract ImmutableModule is ModuleKeys {
INexus public immutable nexus;
/**
* @dev Initialization function for upgradable proxy contracts
* @param _nexus Nexus contract address
*/
constructor(address _nexus) {
require(_nexus != address(0), "Nexus address is zero");
nexus = INexus(_nexus);
}
/**
* @dev Modifier to allow function calls only from the Governor.
*/
modifier onlyGovernor() {
_onlyGovernor();
_;
}
function _onlyGovernor() internal view {
require(msg.sender == _governor(), "Only governor can execute");
}
/**
* @dev Modifier to allow function calls only from the Governance.
* Governance is either Governor address or Governance address.
*/
modifier onlyGovernance() {
require(
msg.sender == _governor() || msg.sender == _governance(),
"Only governance can execute"
);
_;
}
/**
* @dev Returns Governor address from the Nexus
* @return Address of Governor Contract
*/
function _governor() internal view returns (address) {
return nexus.governor();
}
/**
* @dev Returns Governance Module address from the Nexus
* @return Address of the Governance (Phase 2)
*/
function _governance() internal view returns (address) {
return nexus.getModule(KEY_GOVERNANCE);
}
/**
* @dev Return SavingsManager Module address from the Nexus
* @return Address of the SavingsManager Module contract
*/
function _savingsManager() internal view returns (address) {
return nexus.getModule(KEY_SAVINGS_MANAGER);
}
/**
* @dev Return Recollateraliser Module address from the Nexus
* @return Address of the Recollateraliser Module contract (Phase 2)
*/
function _recollateraliser() internal view returns (address) {
return nexus.getModule(KEY_RECOLLATERALISER);
}
/**
* @dev Return Liquidator Module address from the Nexus
* @return Address of the Liquidator Module contract
*/
function _liquidator() internal view returns (address) {
return nexus.getModule(KEY_LIQUIDATOR);
}
/**
* @dev Return ProxyAdmin Module address from the Nexus
* @return Address of the ProxyAdmin Module contract
*/
function _proxyAdmin() internal view returns (address) {
return nexus.getModule(KEY_PROXY_ADMIN);
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IRewardsDistributionRecipient {
function notifyRewardAmount(uint256 reward) external;
function getRewardToken() external view returns (IERC20);
}
interface IRewardsRecipientWithPlatformToken {
function notifyRewardAmount(uint256 reward) external;
function getRewardToken() external view returns (IERC20);
function getPlatformToken() external view returns (IERC20);
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
/**
* @title ModuleKeys
* @author mStable
* @notice Provides system wide access to the byte32 represntations of system modules
* This allows each system module to be able to reference and update one another in a
* friendly way
* @dev keccak256() values are hardcoded to avoid re-evaluation of the constants at runtime.
*/
contract ModuleKeys {
// Governance
// ===========
// keccak256("Governance");
bytes32 internal constant KEY_GOVERNANCE =
0x9409903de1e6fd852dfc61c9dacb48196c48535b60e25abf92acc92dd689078d;
//keccak256("Staking");
bytes32 internal constant KEY_STAKING =
0x1df41cd916959d1163dc8f0671a666ea8a3e434c13e40faef527133b5d167034;
//keccak256("ProxyAdmin");
bytes32 internal constant KEY_PROXY_ADMIN =
0x96ed0203eb7e975a4cbcaa23951943fa35c5d8288117d50c12b3d48b0fab48d1;
// mStable
// =======
// keccak256("OracleHub");
bytes32 internal constant KEY_ORACLE_HUB =
0x8ae3a082c61a7379e2280f3356a5131507d9829d222d853bfa7c9fe1200dd040;
// keccak256("Manager");
bytes32 internal constant KEY_MANAGER =
0x6d439300980e333f0256d64be2c9f67e86f4493ce25f82498d6db7f4be3d9e6f;
//keccak256("Recollateraliser");
bytes32 internal constant KEY_RECOLLATERALISER =
0x39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f;
//keccak256("MetaToken");
bytes32 internal constant KEY_META_TOKEN =
0xea7469b14936af748ee93c53b2fe510b9928edbdccac3963321efca7eb1a57a2;
// keccak256("SavingsManager");
bytes32 internal constant KEY_SAVINGS_MANAGER =
0x12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf1;
// keccak256("Liquidator");
bytes32 internal constant KEY_LIQUIDATOR =
0x1e9cb14d7560734a61fa5ff9273953e971ff3cd9283c03d8346e3264617933d4;
// keccak256("InterestValidator");
bytes32 internal constant KEY_INTEREST_VALIDATOR =
0xc10a28f028c7f7282a03c90608e38a4a646e136e614e4b07d119280c5f7f839f;
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
/**
* @title INexus
* @dev Basic interface for interacting with the Nexus i.e. SystemKernel
*/
interface INexus {
function governor() external view returns (address);
function getModule(bytes32 key) external view returns (address);
function proposeModule(bytes32 _key, address _addr) external;
function cancelProposedModule(bytes32 _key) external;
function acceptProposedModule(bytes32 _key) external;
function acceptProposedModules(bytes32[] calldata _keys) external;
function requestLockModule(bytes32 _key) external;
function cancelLockModule(bytes32 _key) external;
function lockModule(bytes32 _key) external;
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MassetHelpers } from "../../shared/MassetHelpers.sol";
/**
* @title PlatformTokenVendor
* @author mStable
* @notice Stores platform tokens for distributing to StakingReward participants
* @dev Only deploy this during the constructor of a given StakingReward contract
*/
contract PlatformTokenVendor {
IERC20 public immutable platformToken;
address public immutable parentStakingContract;
/** @dev Simple constructor that stores the parent address */
constructor(IERC20 _platformToken) {
parentStakingContract = msg.sender;
platformToken = _platformToken;
MassetHelpers.safeInfiniteApprove(address(_platformToken), msg.sender);
}
/**
* @dev Re-approves the StakingReward contract to spend the platform token.
* Just incase for some reason approval has been reset.
*/
function reApproveOwner() external {
MassetHelpers.safeInfiniteApprove(address(platformToken), parentStakingContract);
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title MassetHelpers
* @author mStable
* @notice Helper functions to facilitate minting and redemption from off chain
* @dev VERSION: 1.0
* DATE: 2020-03-28
*/
library MassetHelpers {
using SafeERC20 for IERC20;
function transferReturnBalance(
address _sender,
address _recipient,
address _bAsset,
uint256 _qty
) internal returns (uint256 receivedQty, uint256 recipientBalance) {
uint256 balBefore = IERC20(_bAsset).balanceOf(_recipient);
IERC20(_bAsset).safeTransferFrom(_sender, _recipient, _qty);
recipientBalance = IERC20(_bAsset).balanceOf(_recipient);
receivedQty = recipientBalance - balBefore;
}
function safeInfiniteApprove(address _asset, address _spender) internal {
IERC20(_asset).safeApprove(_spender, 0);
IERC20(_asset).safeApprove(_spender, 2**256 - 1);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) private pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}// ███████╗░█████╗░██████╗░██████╗░███████╗██████╗░░░░███████╗██╗
// ╚════██║██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗░░░██╔════╝██║
// ░░███╔═╝███████║██████╔╝██████╔╝█████╗░░██████╔╝░░░█████╗░░██║
// ██╔══╝░░██╔══██║██╔═══╝░██╔═══╝░██╔══╝░░██╔══██╗░░░██╔══╝░░██║
// ███████╗██║░░██║██║░░░░░██║░░░░░███████╗██║░░██║██╗██║░░░░░██║
// ╚══════╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░░░░╚══════╝╚═╝░░╚═╝╚═╝╚═╝░░░░░╚═╝
// Copyright (C) 2021 zapper
// Copyright (c) 2018 Tasuku Nakamura
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
///@author Zapper
///@notice This contract checks if a message has been signed by a verified signer via personal_sign.
// SPDX-License-Identifier: GPLv2
pragma solidity ^0.8.0;
library SignatureVerifier {
function verify(
address signer,
address account,
uint256[] calldata ids,
bytes calldata signature
) external pure returns (bool) {
bytes32 messageHash = getMessageHash(account, ids);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == signer;
}
function verify(
address signer,
uint256 id,
address[] calldata accounts,
bytes calldata signature
) external pure returns (bool) {
bytes32 messageHash = getMessageHash(id, accounts);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == signer;
}
function getMessageHash(address account, uint256[] memory ids) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(account, ids));
}
function getMessageHash(uint256 id, address[] memory accounts) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(id, accounts));
}
function getEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
internal
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory signature)
internal
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(signature.length == 65, "invalid signature length");
//solium-disable-next-line
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := byte(0, mload(add(signature, 96)))
}
}
}// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.6;
import "../deps/GamifiedTokenStructs.sol";
interface IQuestManager {
event QuestAdded(
address questMaster,
uint256 id,
QuestType model,
uint16 multiplier,
QuestStatus status,
uint32 expiry
);
event QuestCompleteQuests(address indexed user, uint256[] ids);
event QuestCompleteUsers(uint256 indexed questId, address[] accounts);
event QuestExpired(uint16 indexed id);
event QuestMaster(address oldQuestMaster, address newQuestMaster);
event QuestSeasonEnded();
event QuestSigner(address oldQuestSigner, address newQuestSigner);
event StakedTokenAdded(address stakedToken);
// GETTERS
function balanceData(address _account) external view returns (QuestBalance memory);
function getQuest(uint256 _id) external view returns (Quest memory);
function hasCompleted(address _account, uint256 _id) external view returns (bool);
function questMaster() external view returns (address);
function seasonEpoch() external view returns (uint32);
// ADMIN
function addQuest(
QuestType _model,
uint8 _multiplier,
uint32 _expiry
) external;
function addStakedToken(address _stakedToken) external;
function expireQuest(uint16 _id) external;
function setQuestMaster(address _newQuestMaster) external;
function setQuestSigner(address _newQuestSigner) external;
function startNewQuestSeason() external;
// USER
function completeUserQuests(
address _account,
uint256[] memory _ids,
bytes calldata _signature
) external;
function completeQuestUsers(
uint256 _questId,
address[] memory _accounts,
bytes calldata _signature
) external;
function checkForSeasonFinish(address _account) external returns (uint8 newQuestMultiplier);
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
}
},
"libraries": {
"contracts/rewards/staking/PlatformTokenVendorFactory.sol": {
"PlatformTokenVendorFactory": "0xfb73476911c5e84556a5bf953644b7ef50f6cbc5"
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_nexus","type":"address"},{"internalType":"address","name":"_rewardsToken","type":"address"},{"internalType":"address","name":"_questManager","type":"address"},{"internalType":"address","name":"_stakedToken","type":"address"},{"internalType":"uint256","name":"_cooldownSeconds","type":"uint256"},{"internalType":"uint256","name":"_unstakeWindow","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"percentage","type":"uint256"}],"name":"Cooldown","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"}],"name":"CooldownExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"fromDelegate","type":"address"},{"indexed":true,"internalType":"address","name":"toDelegate","type":"address"}],"name":"DelegateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegate","type":"address"},{"indexed":false,"internalType":"uint256","name":"previousBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBalance","type":"uint256"}],"name":"DelegateVotesChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"hook","type":"address"}],"name":"GovernanceHookChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"Recollateralised","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"SlashRateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"delegatee","type":"address"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WrapperBlacklisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WrapperWhitelisted","type":"event"},{"inputs":[],"name":"COOLDOWN_SECONDS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARDS_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKED_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSTAKE_WINDOW","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_nameArg","type":"bytes32"},{"internalType":"bytes32","name":"_symbolArg","type":"bytes32"},{"internalType":"address","name":"_rewardsDistributorArg","type":"address"}],"name":"__StakedToken_init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint8","name":"_newMultiplier","type":"uint8"}],"name":"applyQuestMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"balanceData","outputs":[{"components":[{"internalType":"uint88","name":"raw","type":"uint88"},{"internalType":"uint32","name":"weightedTimestamp","type":"uint32"},{"internalType":"uint8","name":"timeMultiplier","type":"uint8"},{"internalType":"uint8","name":"questMultiplier","type":"uint8"},{"internalType":"uint32","name":"cooldownTimestamp","type":"uint32"},{"internalType":"uint88","name":"cooldownUnits","type":"uint88"}],"internalType":"struct Balance","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wrapper","type":"address"}],"name":"blackListWrapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_weightedTimestamp","type":"uint32"}],"name":"calcRedemptionFeeRate","outputs":[{"internalType":"uint256","name":"_feeRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newRate","type":"uint256"}],"name":"changeSlashingPercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint32","name":"pos","type":"uint32"}],"name":"checkpoints","outputs":[{"components":[{"internalType":"uint32","name":"fromBlock","type":"uint32"},{"internalType":"uint224","name":"votes","type":"uint224"}],"internalType":"struct GamifiedVotingToken.Checkpoint","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"}],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"compoundRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"createLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"delegates","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"earned","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyRecollateralisation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endCooldown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getPastTotalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getPastVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalData","outputs":[{"internalType":"uint32","name":"periodFinish","type":"uint32"},{"internalType":"uint32","name":"lastUpdateTime","type":"uint32"},{"internalType":"uint96","name":"rewardRate","type":"uint96"},{"internalType":"uint96","name":"rewardPerTokenStored","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hasPriceCoeff","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"increaseLockAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_nameArg","type":"bytes32"},{"internalType":"bytes32","name":"_symbolArg","type":"bytes32"},{"internalType":"address","name":"_rewardsDistributorArg","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nexus","outputs":[{"internalType":"contract INexus","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_reward","type":"uint256"}],"name":"notifyRewardAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"numCheckpoints","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingAdditionalReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"questManager","outputs":[{"internalType":"contract QuestManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"rawBalanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"reviewTimestamp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardTokenVendor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsDistributor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"safetyData","outputs":[{"internalType":"uint128","name":"collateralisationRatio","type":"uint128"},{"internalType":"uint128","name":"slashingPercentage","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newHook","type":"address"}],"name":"setGovernanceHook","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rewardsDistributor","type":"address"}],"name":"setRewardsDistribution","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_delegatee","type":"address"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bool","name":"_exitCooldown","type":"bool"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_units","type":"uint256"}],"name":"startCooldown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userData","outputs":[{"internalType":"uint128","name":"rewardPerTokenPaid","type":"uint128"},{"internalType":"uint128","name":"rewards","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"userPriceCoeff","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wrapper","type":"address"}],"name":"whitelistWrapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistedWrappers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"bool","name":"_amountIncludesFee","type":"bool"},{"internalType":"bool","name":"_exitCooldown","type":"bool"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
6101606040523480156200001257600080fd5b506040516200533b3803806200533b83398101604081905262000035916200011b565b85858585858560008686868383838383838381806001600160a01b038116620000a45760405162461bcd60e51b815260206004820152601560248201527f4e657875732061646472657373206973207a65726f0000000000000000000000604482015260640160405180910390fd5b6001600160601b0319606091821b811660805292811b831660a05294851b821660c0525091151560f81b60e052509a901b9099166101005250505061012094909452505061014052506200018a9950505050505050505050565b80516001600160a01b03811681146200011657600080fd5b919050565b60008060008060008060c087890312156200013557600080fd5b6200014087620000fe565b95506200015060208801620000fe565b94506200016060408801620000fe565b93506200017060608801620000fe565b92506080870151915060a087015190509295509295509295565b60805160601c60a05160601c60c05160601c60e05160f81c6101005160601c610120516101405161508a620002b1600039600081816104e301528181611b71015281816127a40152612f750152600081816106ff01528181611b9501528181612737015281816127c50152612f9601526000818161048a015281816116ed015281816117970152818161183c01528181611a1201528181611adc015281816121df01526129d6015260008181610a3101528181612ab10152818161359301526140eb015260008181610a580152818161123c015281816134de015261406701526000818161069b0152818161085c01528181610ce80152818161171901528181612dee0152613f8e0152600081816107e4015281816130980152613241015261508a6000f3fe608060405234801561001057600080fd5b506004361061038d5760003560e01c806370a08231116101de578063abe50f191161010f578063c8910913116100ad578063d9a2ee571161007c578063d9a2ee5714610a2c578063dc1627eb14610a53578063e9fad8ee14610a7a578063f1127ed814610a8257600080fd5b8063c8910913146109b4578063cd3daf9d146109e8578063d09ad565146109f0578063d279c19114610a1957600080fd5b8063c00c9f7f116100e9578063c00c9f7f14610847578063c19fd2171461084f578063c560107214610857578063c822ddb91461087e57600080fd5b8063abe50f1914610819578063b52c05fe1461082c578063b88a802f1461083f57600080fd5b8063830698851161017c57806395d89b411161015657806395d89b41146107c45780639ab24eb0146107cc578063a3f5c1d2146107df578063a694fc3a1461080657600080fd5b806383069885146107755780638e539e8c1461077e578063943182141461079157600080fd5b806379172222116101b857806379172222146107345780637acb7757146107475780637c098a1b1461075a57806380faa57d1461076d57600080fd5b806370a08231146106e757806372b49d63146106fa5780637446e7ad1461072157600080fd5b8063478dc62f116102c35780635fcddddf1161026157806365c787751161023057806365c7877514610673578063672e1fd81461068657806369940d79146106995780636fcfff45146106bf57600080fd5b80635fcddddf146105cb578063612556bf146105de578063613ce410146105f15780636154913b1461066057600080fd5b8063587cde1e1161029d578063587cde1e1461057f57806358b235dd146105925780635c19a95c146105a55780635fa456dc146105b857600080fd5b8063478dc62f14610551578063519b9a3f1461056457806357c37b691461056c57600080fd5b8063312f6b83116103305780633a46b1a81161030a5780633a46b1a8146105055780633c6b16ab146105185780633f2a55401461052b578063403f44471461053e57600080fd5b8063312f6b8314610485578063313ce567146104c4578063359c4a96146104de57600080fd5b806312064c341161036c57806312064c341461040b57806318160ddd1461045e57806319762143146104665780631be052891461047b57600080fd5b80628cc262146103925780630552c61c146103b857806306fdde03146103f6575b600080fd5b6103a56103a0366004614b07565b610abf565b6040519081526020015b60405180910390f35b609d546103d6906001600160801b0380821691600160801b90041682565b604080516001600160801b039384168152929091166020830152016103af565b6103fe610ad8565b6040516103af9190614d76565b610449610419366004614b07565b6001600160a01b03166000908152603a60205260409020546001600160581b0380821692600160a81b9092041690565b604080519283526020830191909152016103af565b6103a5610aea565b610479610474366004614b07565b610b3c565b005b6103a562093a8081565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016103af565b6104cc601281565b60405160ff90911681526020016103af565b6103a57f000000000000000000000000000000000000000000000000000000000000000081565b6103a5610513366004614b41565b610b66565b610479610526366004614c31565b610be5565b6033546104ac906001600160a01b031681565b61047961054c366004614c31565b610e8a565b61047961055f366004614c31565b610ee4565b610479610fa3565b61047961057a366004614b07565b611030565b6104ac61058d366004614b07565b611082565b6103a56105a0366004614d22565b6110b2565b6104796105b3366004614b07565b611168565b6104796105c6366004614c88565b611172565b6104796105d9366004614b07565b611184565b6104796105ec366004614b07565b6111dd565b6035546106299063ffffffff80821691600160201b8104909116906001600160601b03600160401b8204811691600160a01b90041684565b6040805163ffffffff95861681529490931660208501526001600160601b03918216928401929092521660608201526080016103af565b61047961066e366004614ba2565b611239565b610479610681366004614b07565b61136a565b610479610694366004614c31565b611373565b7f00000000000000000000000000000000000000000000000000000000000000006104ac565b6106d26106cd366004614b07565b61137c565b60405163ffffffff90911681526020016103af565b6103a56106f5366004614b07565b61139e565b6103a57f000000000000000000000000000000000000000000000000000000000000000081565b61047961072f366004614bf8565b61142e565b610479610742366004614bf8565b6114dd565b610479610755366004614c63565b611556565b6034546104ac906001600160a01b031681565b6103a5611566565b6103a560375481565b6103a561078c366004614c31565b61157d565b6107b461079f366004614b07565b609e6020526000908152604090205460ff1681565b60405190151581526020016103af565b6103fe6115d9565b6103a56107da366004614b07565b6115e6565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b610479610814366004614c31565b610ed5565b610479610827366004614cdb565b61166c565b61047961083a366004614d00565b611678565b610479611684565b61047961168f565b61047961193c565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b61094961088c366004614b07565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b03166000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b81049092166080820152600160a81b90910490911660a082015290565b6040516103af9190600060c0820190506001600160581b03808451168352602084015163ffffffff808216602086015260ff604087015116604086015260ff606087015116606086015280608087015116608086015250508060a08501511660a08401525092915050565b6103d66109c2366004614b07565b6036602052600090815260409020546001600160801b0380821691600160801b90041682565b6103a5611b2f565b6103a56109fe366004614b07565b6001600160a01b03166000908152603b602052604090205490565b610479610a27366004614b07565b611b41565b6107b47f000000000000000000000000000000000000000000000000000000000000000081565b6104ac7f000000000000000000000000000000000000000000000000000000000000000081565b610479611b4a565b610a95610a90366004614b6d565b611c2b565b60408051825163ffffffff1681526020928301516001600160e01b031692810192909252016103af565b6000610ad282610acd611b2f565b611cae565b92915050565b6060610ae5603854611d68565b905090565b606c5460009080610afd57600091505090565b606c610b0a600183614f38565b81548110610b1a57610b1a614ff6565b600091825260209091200154600160201b90046001600160e01b031692915050565b610b44611e83565b603380546001600160a01b0319166001600160a01b0392909216919091179055565b6000438210610bbc5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e65640060448201526064015b60405180910390fd5b6001600160a01b0383166000908152606b60205260409020610bde9083611eeb565b9392505050565b6033546001600160a01b03163314610c3f5760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206973206e6f7420726577617264206469737472696275746f726044820152606401610bb3565b6000610c4a81611fa7565b69d3c21bcecceda10000008210610ca35760405162461bcd60e51b815260206004820181905260248201527f4e6f74696679206d6f7265207468616e2061206d696c6c696f6e20756e6974736044820152606401610bb3565b603754429060011015610cd1576001603754610cbf9190614f38565b610cc99084614e20565b600160375592505b8215610d1157603454610d11906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081169116856120a6565b60355463ffffffff168110610d6a57610d35610d3062093a8085614e88565b612109565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b19909216919091179055610df4565b603554600090610d8190839063ffffffff16614f38565b603554909150600090610da490600160401b90046001600160601b031683614ec2565b9050610dc162093a80610db78388614e20565b610d309190614e88565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b1990921691909117905550505b610dfd81612175565b6035805463ffffffff92909216600160201b0267ffffffff0000000019909216919091179055610e38610e3362093a8083614e20565b612175565b6035805463ffffffff191663ffffffff929092169190911790556040518381527fde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d9060200160405180910390a1505050565b610e933361139e565b610ed55760405162461bcd60e51b81526020600482015260136024820152724e6f7468696e6720746f20696e63726561736560681b6044820152606401610bb3565b610ee1816000806121da565b50565b610eec611e83565b610ef461221a565b6706f05b59d3b20000811115610f405760405162461bcd60e51b815260206004820152601160248201527043616e6e6f74206578636565642035302560781b6044820152606401610bb3565b610f498161227c565b609d80546001600160801b03928316600160801b0292169190911790556040517f130d585ea14fd64e9d6a9e64231dba9b8692207d164bd5e38d66c02ae9929a4490610f989083815260200190565b60405180910390a150565b336000908152603a6020526040902054600160881b900463ffffffff16610ffa5760405162461bcd60e51b815260206004820152600b60248201526a27379031b7b7b63237bbb760a91b6044820152606401610bb3565b611003336122e5565b60405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a2565b611038611e83565b606d80546001600160a01b0319166001600160a01b0383169081179091556040517f70f5968ed58c0d81c7a8bca106614e2ea19375c430722f9f0ad68de8fd74f97890600090a250565b6001600160a01b038082166000908152606a602052604081205490911680156110ab5780610bde565b5090919050565b60008062093a806110c963ffffffff851642614f38565b6110db90670de0b6b3a7640000614ec2565b6110e59190614e88565b90506729a2241af62c000081111561115657611119611114826fe1b1e5f90f944d6e1c9e66c000000000614e88565b6123d8565b6111269062989680614ec2565b91506658d15e17628000821061114c576111476658d15e1762800083614f38565b61114f565b60005b9150611162565b67010a741a4627800091505b50919050565b610ee13382612552565b61117e848484846125d2565b50505050565b61118c611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916905590519182527f6843d4fd51341f35652c364bbf7be3666eacfcfc9ebac9db41bd6e05c432165f9101610f98565b6111e5611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916600117905590519182527f66a41827fb347195a5a3f3b9c5c921310e2879c6715d6f2dff54add1a2fc562e9101610f98565b337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316146112a05760405162461bcd60e51b815260206004820152600c60248201526b139bdd081d995c9a599a595960a21b6044820152606401610bb3565b6001600160a01b0382166112c65760405162461bcd60e51b8152600401610bb390614da9565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906113568483612a4a565b9050801561117e5761117e84838386612b06565b610ee181612b93565b610ee181612c76565b6001600160a01b0381166000908152606b6020526040812054610ad290612175565b6001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152610ad2908390612a4a565b600054610100900460ff1680611447575060005460ff16155b6114635760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611485576000805461ffff19166101011790555b611490848484612d0d565b6114a2609c805460ff19166001179055565b60408051808201909152670de0b6b3a76400008082526000602090920191909152609d55801561117e576000805461ff001916905550505050565b600054610100900460ff16806114f6575060005460ff16155b6115125760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611534576000805461ffff19166101011790555b61153f84848461142e565b801561117e576000805461ff001916905550505050565b611562828260006121da565b5050565b603554600090610ae590429063ffffffff16612d7f565b60004382106115ce5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e6564006044820152606401610bb3565b610ad2606c83611eeb565b6060610ae5603954611d68565b6001600160a01b0381166000908152606b60205260408120548015611659576001600160a01b0383166000908152606b60205260409020611628600183614f38565b8154811061163857611638614ff6565b600091825260209091200154600160201b90046001600160e01b031661165c565b60005b6001600160e01b03169392505050565b611562826000836121da565b611562826000806121da565b61168d33612d8e565b565b609c5460ff166116e15760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610bb3565b609c805460ff191690557f00000000000000000000000000000000000000000000000000000000000000006001600160a01b039081167f0000000000000000000000000000000000000000000000000000000000000000919091161461177f5760405162461bcd60e51b81526020600482015260136024820152724f6e6c7920666f722073616d6520706169727360681b6044820152606401610bb3565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b1580156117e157600080fd5b505afa1580156117f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118199190614c4a565b905061182430612d8e565b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b15801561188657600080fd5b505afa15801561189a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118be9190614c4a565b905060006118cc8383614f38565b90506000811161191e5760405162461bcd60e51b815260206004820152601760248201527f4d75737420636f6d706f756e6420736f6d657468696e670000000000000000006044820152606401610bb3565b61192a81600080612e6e565b5050609c805460ff1916600117905550565b611944613060565b6001600160a01b0316336001600160a01b0316146119a45760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79205265636f6c6c61746572616c69736174696f6e204d6f64756c65006044820152606401610bb3565b6119ac61221a565b609d546119d190600160801b90046001600160801b0316670de0b6b3a7640000614f10565b609d80546fffffffffffffffffffffffffffffffff19166001600160801b03929092169190911790556040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a082319060240160206040518083038186803b158015611a5c57600080fd5b505afa158015611a70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a949190614c4a565b9050611b03611aa1613060565b609d54670de0b6b3a764000090611ac890600160801b90046001600160801b031685614ec2565b611ad29190614e88565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691906120a6565b6040517f8551ab4a9801f4813e5fff4a88eb374487886333f7c4e0aa1ca928a24916d53c90600090a150565b600080611b3a61311a565b5092915050565b610ee181612d8e565b336000908152603a6020526040902054600160881b900463ffffffff16801580611bd057507f0000000000000000000000000000000000000000000000000000000000000000611bc37f00000000000000000000000000000000000000000000000000000000000000006001600160801b038416614e20565b611bcd9190614e20565b42115b15611bfd57600080611be133610419565b9092509050611bf8611bf38284614e20565b612c76565b505050565b336000818152603a6020526040812054610ee192600160a81b9091046001600160581b0316916001906125d2565b60408051808201909152600080825260208201526001600160a01b0383166000908152606b60205260409020805463ffffffff8416908110611c6f57611c6f614ff6565b60009182526020918290206040805180820190915291015463ffffffff81168252600160201b90046001600160e01b0316918101919091529392505050565b6001600160a01b0382166000908152603660205260408120548190611cdc906001600160801b031684614f38565b905080611d135750506001600160a01b038216600090815260366020526040902054600160801b90046001600160801b0316610ad2565b6000611d2882611d228761139e565b90613228565b6001600160a01b038616600090815260366020526040902054909150611d5f908290600160801b90046001600160801b0316614e20565b95945050505050565b606060005b602081108015611d9b5750828160208110611d8a57611d8a614ff6565b1a60f81b6001600160f81b03191615155b15611db25780611daa81614f9b565b915050611d6d565b60008167ffffffffffffffff811115611dcd57611dcd61500c565b6040519080825280601f01601f191660200182016040528015611df7576020820181803683370190505b509050600091505b602082108015611e2d5750838260208110611e1c57611e1c614ff6565b1a60f81b6001600160f81b03191615155b15610bde57838260208110611e4457611e44614ff6565b1a60f81b818381518110611e5a57611e5a614ff6565b60200101906001600160f81b031916908160001a90535081611e7b81614f9b565b925050611dff565b611e8b61323d565b6001600160a01b0316336001600160a01b03161461168d5760405162461bcd60e51b815260206004820152601960248201527f4f6e6c7920676f7665726e6f722063616e2065786563757465000000000000006044820152606401610bb3565b8154600090815b81811015611f4f576000611f068284613298565b905084868281548110611f1b57611f1b614ff6565b60009182526020909120015463ffffffff161115611f3b57809250611f49565b611f46816001614e20565b91505b50611ef2565b8115611f925784611f61600184614f38565b81548110611f7157611f71614ff6565b600091825260209091200154600160201b90046001600160e01b0316611f95565b60005b6001600160e01b031695945050505050565b600080611fb261311a565b90925090508115611bf857611fc682612109565b603580546001600160601b0392909216600160a01b026001600160a01b03909216919091179055611ff681612175565b6035805463ffffffff92909216600160201b0267ffffffff00000000199092169190911790556001600160a01b03831615611bf857604051806040016040528061203f8461227c565b6001600160801b0316815260200161205f61205a8686611cae565b61227c565b6001600160801b039081169091526001600160a01b0385166000908152603660209081526040909120835193909101518216600160801b0292909116919091179055505050565b6040516001600160a01b038316602482015260448101829052611bf890849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526132ef565b60006001600160601b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b6064820152608401610bb3565b5090565b600063ffffffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b6064820152608401610bb3565b61220f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163330866133c1565b611bf8838383612e6e565b609d546001600160801b0316670de0b6b3a76400001461168d5760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79207768696c652066756c6c7920636f6c6c61746572616c69736564006044820152606401610bb3565b60006001600160801b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608401610bb3565b806122ef81611fa7565b6001600160a01b0382166123155760405162461bcd60e51b8152600401610bb390614da9565b600080612321846133f9565b9150915061233282602001516135e9565b6001600160a01b0385166000908152603a60205260408120805460ff93909316600160781b0260ff60781b1984168117825560a0860151939192916123869185916001600160581b03918216911617614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0384166000908152603a6020526040902080546001600160881b0316905561117e8482613682565b6000816123e757506000919050565b816001600160801b82106124005760809190911c9060401b5b600160401b82106124165760409190911c9060201b5b600160201b821061242c5760209190911c9060101b5b6201000082106124415760109190911c9060081b5b61010082106124555760089190911c9060041b5b601082106124685760049190911c9060021b5b600882106124745760011b5b60016124808286614e88565b61248a9083614e20565b901c9050600161249a8286614e88565b6124a49083614e20565b901c905060016124b48286614e88565b6124be9083614e20565b901c905060016124ce8286614e88565b6124d89083614e20565b901c905060016124e88286614e88565b6124f29083614e20565b901c905060016125028286614e88565b61250c9083614e20565b901c9050600161251c8286614e88565b6125269083614e20565b901c905060006125368286614e88565b90508082106125455780611d5f565b509392505050565b919050565b600061255d83611082565b9050600061256a8461139e565b6001600160a01b038581166000818152606a602052604080822080546001600160a01b031916898616908117909155905194955093928616927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a461117e828483613742565b6125da6138fe565b8361261d5760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b609d546001600160801b0316670de0b6b3a7640000146126b057612645338560006001613964565b609d5461266b908490670de0b6b3a764000090611ac8906001600160801b031688614ec2565b6040518481526001600160a01b0384169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a361117e565b336000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b810490921660808201819052600160a81b90920490921660a083015261275c907f000000000000000000000000000000000000000000000000000000000000000090614e20565b42116127a25760405162461bcd60e51b815260206004820152601560248201527424a729aaa32324a1a4a2a72a2fa1a7a7a62227aba760591b6044820152606401610bb3565b7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000826080015163ffffffff166127f99190614e20565b6128039042614f38565b11156128515760405162461bcd60e51b815260206004820152601760248201527f554e5354414b455f57494e444f575f46494e49534845440000000000000000006044820152606401610bb3565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b8304811695840186905260ff600160781b8404811695850195909552600160801b83049094166060840152600160881b82049093166080830152600160a81b900490911660a082015291906128d8906110b2565b905060008561290d57670de0b6b3a76400006128f48382614e20565b6128fe908a614ec2565b6129089190614e88565b61290f565b875b9050600061292583670de0b6b3a7640000614e20565b61293783670de0b6b3a7640000614ec2565b6129419190614e88565b60a08601519091506001600160581b03168083111561299b5760405162461bcd60e51b8152602060048201526016602482015275115e18d959591cc81b585e081dda5d1a191c985dd85b60521b6044820152606401610bb3565b600087806129a857508184145b90506129b73385836000613964565b6129c96129c48486614f38565b613c93565b6129fd6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168b856120a6565b6040518b81526001600160a01b038b169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a35050505050505050505050565b6000606482606001516064612a5f9190614e38565b8351612a6e9160ff1690614ee1565b612a789190614e9c565b6001600160581b03169050606482604001516064612a969190614e38565b612aa39060ff1683614ec2565b612aad9190614e88565b90507f000000000000000000000000000000000000000000000000000000000000000015610ad2576001600160a01b0383166000908152603b602052604090205461271090612afc9083614ec2565b610bde9190614e88565b83612b1081611fa7565b6001600160a01b0385166000908152603a60209081526040909120805460ff60801b1916600160801b60ff861602179055840151612b4d906135e9565b6001600160a01b0386166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055612b8c8584613682565b5050505050565b80612b9d81611fa7565b6001600160a01b038216612bc35760405162461bcd60e51b8152600401610bb390614da9565b600080612bcf846133f9565b915091506000612be283602001516135e9565b9050826040015160ff168160ff161415612c3e5760405162461bcd60e51b815260206004820152601960248201527f4e6f7468696e6720776f72746820706f6b696e672068657265000000000000006044820152606401610bb3565b6001600160a01b0385166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055612b8c8583613682565b612c7f3361139e565b612ccb5760405162461bcd60e51b815260206004820152601b60248201527f494e56414c49445f42414c414e43455f4f4e5f434f4f4c444f574e00000000006044820152606401610bb3565b612cd53382613d1b565b60405181815233907f8a05f911d8ab7fc50fec37ef4ba7f9bfcb1a3c191c81dcd824ad0946c4e20d659060200160405180910390a250565b600054610100900460ff1680612d26575060005460ff16155b612d425760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015612d64576000805461ffff19166101011790555b612d6c613f03565b6038849055603983905561153f82613f6e565b60008183116111625782610bde565b33612d9881611fa7565b33600090815260366020526040902054600160801b90046001600160801b03168015612e655733600090815260366020526040902080546001600160801b03908116909155603454612e1b916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116921690869085166133c1565b604080516001600160801b038316815290516001600160a01b0385169133917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9181900360200190a35b611bf833614045565b612e7661221a565b612e7e6138fe565b82612ec15760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b6001600160a01b03821615612eda57612eda3383612552565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152908280612fd757506000826080015163ffffffff16118015612fd757507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000836080015163ffffffff16612fca9190614e20565b612fd49190614e20565b42115b9050801561300b5760405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a25b61301633868361426a565b604080518681526001600160a01b038616602082015233917f9f9e4044c5742cca66ca090b21552bac14645e68bad7a92364a9d9ff18111a1c910160405180910390a25050505050565b6040516385acd64160e01b81527f39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f60048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906385acd6419060240160206040518083038186803b1580156130e257600080fd5b505afa1580156130f6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae59190614b24565b6000806000613127611566565b6040805160808101825260355463ffffffff8082168352600160201b820416602083018190526001600160601b03600160401b8304811694840194909452600160a01b90910490921660608201529192506000906131859084614f38565b9050806131a25750606001516001600160601b0316939092509050565b60008183604001516001600160601b03166131bd9190614ec2565b905060006131c9610aea565b90508015806131d6575081155b156131f357505050606001516001600160601b0316939092509050565b60006131ff838361458c565b90508085606001516001600160601b031661321a9190614e20565b989597509495505050505050565b6000610bde8383670de0b6b3a76400006145a1565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316630c340a246040518163ffffffff1660e01b815260040160206040518083038186803b1580156130e257600080fd5b600060026132a68184614fb6565b6132b1600286614fb6565b6132bb9190614e20565b6132c59190614e88565b6132d0600284614e88565b6132db600286614e88565b6132e59190614e20565b610bde9190614e20565b6000613344826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166145c09092919063ffffffff16565b805190915015611bf857808060200190518101906133629190614bdb565b611bf85760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610bb3565b6040516001600160a01b038085166024830152831660448201526064810182905261117e9085906323b872dd60e01b906084016120d2565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906134bc8383612a4a565b604051639295ac5960e01b81526001600160a01b0385811660048301529192507f000000000000000000000000000000000000000000000000000000000000000090911690639295ac5990602401602060405180830381600087803b15801561352457600080fd5b505af1158015613538573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061355c9190614d3d565b6001600160a01b0384166000908152603a60205260409020805460ff92909216600160801b0260ff60801b199092169190911790557f0000000000000000000000000000000000000000000000000000000000000000156135e4576135c26127106145cf565b6001600160a01b0384166000908152603b6020526040902061ffff9190911690555b915091565b600063ffffffff82166135fe57506000919050565b600061361063ffffffff841642614f38565b90506277f8808110156136265750600092915050565b62eff10081101561363a5750601492915050565b6301dfe20081101561364f5750601e92915050565b6302cfd3008110156136645750602892915050565b6303bfc4008110156136795750603292915050565b50603c92915050565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152613712908490612a4a565b90508181111561372f57611bf88361372a8484614f38565b614632565b611bf88361373d8385614f38565b61467f565b816001600160a01b0316836001600160a01b0316141580156137645750600081115b15611bf8576001600160a01b038316156137f2576001600160a01b0383166000908152606b60205260408120819061379f906146cc856146d8565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72483836040516137e7929190918252602082015260400190565b60405180910390a250505b6001600160a01b0382161561387b576001600160a01b0382166000908152606b6020526040812081906138289061484f856146d8565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051613870929190918252602082015260400190565b60405180910390a250505b606d546001600160a01b031615611bf857606d546040516216c4a960ea1b81526001600160a01b03858116600483015284811660248301526044820184905290911690635b12a40090606401600060405180830381600087803b1580156138e157600080fd5b505af11580156138f5573d6000803e3d6000fd5b50505050505050565b33321461168d57336000908152609e602052604090205460ff1661168d5760405162461bcd60e51b815260206004820152601a60248201527f4e6f7420612077686974656c697374656420636f6e74726163740000000000006044820152606401610bb3565b8361396e81611fa7565b6001600160a01b0385166139c45760405162461bcd60e51b815260206004820152601d60248201527f45524332303a206275726e2066726f6d207a65726f20616464726573730000006044820152606401610bb3565b6000806139d0876133f9565b9150915060008260a0015183600001516139ea9190614e5d565b6001600160581b031690508415613a83576001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19169055613a2e8161485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613a748161485b565b6001600160581b031660a08401525b868360a001516001600160581b03161015613ae05760405162461bcd60e51b815260206004820152601c60248201527f45524332303a206275726e20616d6f756e74203e2062616c616e6365000000006044820152606401610bb3565b613ae98761485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b03600160a81b808304821694909403169092026001600160a81b039092169190911790558515613bb6576001600160a01b0388166000908152603a6020526040812080546001600160581b03600160a81b820481169391613b6d91859116614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0388166000908152603a6020526040902080546001600160881b031690555b6000613bc3600889614e88565b613bcd9083614f38565b6020850151613be29063ffffffff1642614f38565b613bec9190614ec2565b90506000613bfa8383614e88565b90506000613c0b610e338342614f38565b6001600160a01b038c166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150613c4b826135e9565b6001600160a01b038d166000908152603a60205260409020805460ff60781b1916600160781b60ff8416021790559050613c858c87613682565b505050505050505050505050565b69d3c21bcecceda10000008110613d015760405162461bcd60e51b815260206004820152602c60248201527f43616e6e6f74206e6f746966792077697468206d6f7265207468616e2061206d60448201526b696c6c696f6e20756e69747360a01b6064820152608401610bb3565b8060376000828254613d139190614e20565b909155505050565b81613d2581611fa7565b6001600160a01b038316613d4b5760405162461bcd60e51b8152600401610bb390614da9565b600080613d57856133f9565b9150915060008260a001518360000151613d719190614e5d565b9050600085118015613d8c5750806001600160581b03168511155b613dd85760405162461bcd60e51b815260206004820152601e60248201527f4d7573742063686f6f7365206265747765656e203020616e64203130302500006044820152606401610bb3565b613de583602001516135e9565b6001600160a01b0387166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055613e238561485b565b613e2d9082614f4f565b6001600160a01b0387166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b0392909216919091179055613e7042612175565b6001600160a01b0387166000908152603a60205260409020805463ffffffff92909216600160881b0263ffffffff60881b19909216919091179055613eb48561485b565b6001600160a01b0387166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613efb8683613682565b505050505050565b600054610100900460ff1680613f1c575060005460ff16155b613f385760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015613f5a576000805461ffff19166101011790555b8015610ee1576000805461ff001916905550565b613f7781610b44565b604051633e50523160e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482015273fb73476911c5e84556a5bf953644b7ef50f6cbc590633e5052319060240160206040518083038186803b158015613fea57600080fd5b505af4158015613ffe573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140229190614b24565b603480546001600160a01b0319166001600160a01b039290921691909117905550565b604051639295ac5960e01b81526001600160a01b0382811660048301526000917f000000000000000000000000000000000000000000000000000000000000000090911690639295ac5990602401602060405180830381600087803b1580156140ad57600080fd5b505af11580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614d3d565b905060007f0000000000000000000000000000000000000000000000000000000000000000614115576000614134565b6001600160a01b0383166000908152603b602052604090205461271014155b6001600160a01b0384166000908152603a602052604090205490915060ff838116600160801b909204161415806141685750805b15611bf8576001600160a01b0383166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a08201526141fd908590612a4a565b6001600160a01b0385166000908152603a60205260409020805460ff60801b1916600160801b60ff871602179055905081156142605761423e6127106145cf565b6001600160a01b0385166000908152603b6020526040902061ffff9190911690555b61117e8482613682565b8261427481611fa7565b6001600160a01b0384166142ca5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610bb3565b6000806142d6866133f9565b9150915060008260a0015183600001516142f09190614e5d565b90506142fb8661485b565b83516143079190614e5d565b6001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b039290921691909117905584156143c65760a08301516001600160a01b0388166000908152603a60205260408120805490919061437d9084906001600160581b0316614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0387166000908152603a6020526040902080546001600160881b031690555b602083015163ffffffff1661449c576143de42612175565b6001600160a01b0388166000908152603a6020908152604091829020805463ffffffff60581b198116600160581b63ffffffff968716810291821793849055855160c0810187526001600160581b039384169284169290921782528304861693810193909352600160781b820460ff90811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a082015261449490889061372a908290612a4a565b50505061117e565b6000816001600160581b0316846020015163ffffffff16426144be9190614f38565b6144c89190614ec2565b905060006144d7600289614e88565b6144ea906001600160581b038516614e20565b6144f49083614e88565b90506000614505610e338342614f38565b6001600160a01b038b166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150614545826135e9565b6001600160a01b038c166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055905061457f8b87613682565b5050505050505050505050565b600081612afc670de0b6b3a764000085614ec2565b6000816145ae8486614ec2565b6145b89190614e88565b949350505050565b60606145b884846000856148c3565b600061ffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201526536206269747360d01b6064820152608401610bb3565b6040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562600083836149eb565b6040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562826000836149eb565b6000610bde8284614f38565b82546000908190801561472257856146f1600183614f38565b8154811061470157614701614ff6565b600091825260209091200154600160201b90046001600160e01b0316614725565b60005b6001600160e01b0316925061473e83858763ffffffff16565b915060008111801561477c57504386614758600184614f38565b8154811061476857614768614ff6565b60009182526020909120015463ffffffff16145b156147dc5761478a82614a51565b86614796600184614f38565b815481106147a6576147a6614ff6565b9060005260206000200160000160046101000a8154816001600160e01b0302191690836001600160e01b03160217905550614846565b8560405180604001604052806147f143612175565b63ffffffff16815260200161480585614a51565b6001600160e01b039081169091528254600181018455600093845260209384902083519490930151909116600160201b0263ffffffff909316929092179101555b50935093915050565b6000610bde8284614e20565b60006001600160581b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203860448201526538206269747360d01b6064820152608401610bb3565b6060824710156149245760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610bb3565b843b6149725760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bb3565b600080866001600160a01b0316858760405161498e9190614d5a565b60006040518083038185875af1925050503d80600081146149cb576040519150601f19603f3d011682016040523d82523d6000602084013e6149d0565b606091505b50915091506149e0828286614aba565b979650505050505050565b6001600160a01b0383161580614a0857506001600160a01b038216155b15614a3657614a33606c6001600160a01b03841615614a295761484f614a2d565b6146cc5b836146d8565b50505b611bf8614a4284611082565b614a4b84611082565b83613742565b60006001600160e01b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b6064820152608401610bb3565b60608315614ac9575081610bde565b825115614ad95782518084602001fd5b8160405162461bcd60e51b8152600401610bb39190614d76565b803563ffffffff8116811461254d57600080fd5b600060208284031215614b1957600080fd5b8135610bde81615022565b600060208284031215614b3657600080fd5b8151610bde81615022565b60008060408385031215614b5457600080fd5b8235614b5f81615022565b946020939093013593505050565b60008060408385031215614b8057600080fd5b8235614b8b81615022565b9150614b9960208401614af3565b90509250929050565b60008060408385031215614bb557600080fd5b8235614bc081615022565b91506020830135614bd081615045565b809150509250929050565b600060208284031215614bed57600080fd5b8151610bde81615037565b600080600060608486031215614c0d57600080fd5b83359250602084013591506040840135614c2681615022565b809150509250925092565b600060208284031215614c4357600080fd5b5035919050565b600060208284031215614c5c57600080fd5b5051919050565b60008060408385031215614c7657600080fd5b823591506020830135614bd081615022565b60008060008060808587031215614c9e57600080fd5b843593506020850135614cb081615022565b92506040850135614cc081615037565b91506060850135614cd081615037565b939692955090935050565b60008060408385031215614cee57600080fd5b823591506020830135614bd081615037565b60008060408385031215614d1357600080fd5b50508035926020909101359150565b600060208284031215614d3457600080fd5b610bde82614af3565b600060208284031215614d4f57600080fd5b8151610bde81615045565b60008251614d6c818460208701614f6f565b9190910192915050565b6020815260008251806020840152614d95816040850160208701614f6f565b601f01601f19169190910160400192915050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008219821115614e3357614e33614fca565b500190565b600060ff821660ff84168060ff03821115614e5557614e55614fca565b019392505050565b60006001600160581b03808316818516808303821115614e7f57614e7f614fca565b01949350505050565b600082614e9757614e97614fe0565b500490565b60006001600160581b0380841680614eb657614eb6614fe0565b92169190910492915050565b6000816000190483118215151615614edc57614edc614fca565b500290565b60006001600160581b0380831681851681830481118215151615614f0757614f07614fca565b02949350505050565b60006001600160801b0383811690831681811015614f3057614f30614fca565b039392505050565b600082821015614f4a57614f4a614fca565b500390565b60006001600160581b0383811690831681811015614f3057614f30614fca565b60005b83811015614f8a578181015183820152602001614f72565b8381111561117e5750506000910152565b6000600019821415614faf57614faf614fca565b5060010190565b600082614fc557614fc5614fe0565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610ee157600080fd5b8015158114610ee157600080fd5b60ff81168114610ee157600080fdfea2646970667358221220155d7c66c220678cc50928f92b1daeec0245e02d64d88f549ddb0b05148db48464736f6c63430008060033000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb3000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2000000000000000000000000861f12764780896fd783ea615dd55df0ff865752000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd200000000000000000000000000000000000000000000000000000000001baf800000000000000000000000000000000000000000000000000000000000127500
Deployed Bytecode
0x608060405234801561001057600080fd5b506004361061038d5760003560e01c806370a08231116101de578063abe50f191161010f578063c8910913116100ad578063d9a2ee571161007c578063d9a2ee5714610a2c578063dc1627eb14610a53578063e9fad8ee14610a7a578063f1127ed814610a8257600080fd5b8063c8910913146109b4578063cd3daf9d146109e8578063d09ad565146109f0578063d279c19114610a1957600080fd5b8063c00c9f7f116100e9578063c00c9f7f14610847578063c19fd2171461084f578063c560107214610857578063c822ddb91461087e57600080fd5b8063abe50f1914610819578063b52c05fe1461082c578063b88a802f1461083f57600080fd5b8063830698851161017c57806395d89b411161015657806395d89b41146107c45780639ab24eb0146107cc578063a3f5c1d2146107df578063a694fc3a1461080657600080fd5b806383069885146107755780638e539e8c1461077e578063943182141461079157600080fd5b806379172222116101b857806379172222146107345780637acb7757146107475780637c098a1b1461075a57806380faa57d1461076d57600080fd5b806370a08231146106e757806372b49d63146106fa5780637446e7ad1461072157600080fd5b8063478dc62f116102c35780635fcddddf1161026157806365c787751161023057806365c7877514610673578063672e1fd81461068657806369940d79146106995780636fcfff45146106bf57600080fd5b80635fcddddf146105cb578063612556bf146105de578063613ce410146105f15780636154913b1461066057600080fd5b8063587cde1e1161029d578063587cde1e1461057f57806358b235dd146105925780635c19a95c146105a55780635fa456dc146105b857600080fd5b8063478dc62f14610551578063519b9a3f1461056457806357c37b691461056c57600080fd5b8063312f6b83116103305780633a46b1a81161030a5780633a46b1a8146105055780633c6b16ab146105185780633f2a55401461052b578063403f44471461053e57600080fd5b8063312f6b8314610485578063313ce567146104c4578063359c4a96146104de57600080fd5b806312064c341161036c57806312064c341461040b57806318160ddd1461045e57806319762143146104665780631be052891461047b57600080fd5b80628cc262146103925780630552c61c146103b857806306fdde03146103f6575b600080fd5b6103a56103a0366004614b07565b610abf565b6040519081526020015b60405180910390f35b609d546103d6906001600160801b0380821691600160801b90041682565b604080516001600160801b039384168152929091166020830152016103af565b6103fe610ad8565b6040516103af9190614d76565b610449610419366004614b07565b6001600160a01b03166000908152603a60205260409020546001600160581b0380821692600160a81b9092041690565b604080519283526020830191909152016103af565b6103a5610aea565b610479610474366004614b07565b610b3c565b005b6103a562093a8081565b6104ac7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd281565b6040516001600160a01b0390911681526020016103af565b6104cc601281565b60405160ff90911681526020016103af565b6103a57f000000000000000000000000000000000000000000000000000000000012750081565b6103a5610513366004614b41565b610b66565b610479610526366004614c31565b610be5565b6033546104ac906001600160a01b031681565b61047961054c366004614c31565b610e8a565b61047961055f366004614c31565b610ee4565b610479610fa3565b61047961057a366004614b07565b611030565b6104ac61058d366004614b07565b611082565b6103a56105a0366004614d22565b6110b2565b6104796105b3366004614b07565b611168565b6104796105c6366004614c88565b611172565b6104796105d9366004614b07565b611184565b6104796105ec366004614b07565b6111dd565b6035546106299063ffffffff80821691600160201b8104909116906001600160601b03600160401b8204811691600160a01b90041684565b6040805163ffffffff95861681529490931660208501526001600160601b03918216928401929092521660608201526080016103af565b61047961066e366004614ba2565b611239565b610479610681366004614b07565b61136a565b610479610694366004614c31565b611373565b7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26104ac565b6106d26106cd366004614b07565b61137c565b60405163ffffffff90911681526020016103af565b6103a56106f5366004614b07565b61139e565b6103a57f00000000000000000000000000000000000000000000000000000000001baf8081565b61047961072f366004614bf8565b61142e565b610479610742366004614bf8565b6114dd565b610479610755366004614c63565b611556565b6034546104ac906001600160a01b031681565b6103a5611566565b6103a560375481565b6103a561078c366004614c31565b61157d565b6107b461079f366004614b07565b609e6020526000908152604090205460ff1681565b60405190151581526020016103af565b6103fe6115d9565b6103a56107da366004614b07565b6115e6565b6104ac7f000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb381565b610479610814366004614c31565b610ed5565b610479610827366004614cdb565b61166c565b61047961083a366004614d00565b611678565b610479611684565b61047961168f565b61047961193c565b6104ac7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd281565b61094961088c366004614b07565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b03166000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b81049092166080820152600160a81b90910490911660a082015290565b6040516103af9190600060c0820190506001600160581b03808451168352602084015163ffffffff808216602086015260ff604087015116604086015260ff606087015116606086015280608087015116608086015250508060a08501511660a08401525092915050565b6103d66109c2366004614b07565b6036602052600090815260409020546001600160801b0380821691600160801b90041682565b6103a5611b2f565b6103a56109fe366004614b07565b6001600160a01b03166000908152603b602052604090205490565b610479610a27366004614b07565b611b41565b6107b47f000000000000000000000000000000000000000000000000000000000000000081565b6104ac7f000000000000000000000000861f12764780896fd783ea615dd55df0ff86575281565b610479611b4a565b610a95610a90366004614b6d565b611c2b565b60408051825163ffffffff1681526020928301516001600160e01b031692810192909252016103af565b6000610ad282610acd611b2f565b611cae565b92915050565b6060610ae5603854611d68565b905090565b606c5460009080610afd57600091505090565b606c610b0a600183614f38565b81548110610b1a57610b1a614ff6565b600091825260209091200154600160201b90046001600160e01b031692915050565b610b44611e83565b603380546001600160a01b0319166001600160a01b0392909216919091179055565b6000438210610bbc5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e65640060448201526064015b60405180910390fd5b6001600160a01b0383166000908152606b60205260409020610bde9083611eeb565b9392505050565b6033546001600160a01b03163314610c3f5760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206973206e6f7420726577617264206469737472696275746f726044820152606401610bb3565b6000610c4a81611fa7565b69d3c21bcecceda10000008210610ca35760405162461bcd60e51b815260206004820181905260248201527f4e6f74696679206d6f7265207468616e2061206d696c6c696f6e20756e6974736044820152606401610bb3565b603754429060011015610cd1576001603754610cbf9190614f38565b610cc99084614e20565b600160375592505b8215610d1157603454610d11906001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd281169116856120a6565b60355463ffffffff168110610d6a57610d35610d3062093a8085614e88565b612109565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b19909216919091179055610df4565b603554600090610d8190839063ffffffff16614f38565b603554909150600090610da490600160401b90046001600160601b031683614ec2565b9050610dc162093a80610db78388614e20565b610d309190614e88565b603580546001600160601b0392909216600160401b026bffffffffffffffffffffffff60401b1990921691909117905550505b610dfd81612175565b6035805463ffffffff92909216600160201b0267ffffffff0000000019909216919091179055610e38610e3362093a8083614e20565b612175565b6035805463ffffffff191663ffffffff929092169190911790556040518381527fde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d9060200160405180910390a1505050565b610e933361139e565b610ed55760405162461bcd60e51b81526020600482015260136024820152724e6f7468696e6720746f20696e63726561736560681b6044820152606401610bb3565b610ee1816000806121da565b50565b610eec611e83565b610ef461221a565b6706f05b59d3b20000811115610f405760405162461bcd60e51b815260206004820152601160248201527043616e6e6f74206578636565642035302560781b6044820152606401610bb3565b610f498161227c565b609d80546001600160801b03928316600160801b0292169190911790556040517f130d585ea14fd64e9d6a9e64231dba9b8692207d164bd5e38d66c02ae9929a4490610f989083815260200190565b60405180910390a150565b336000908152603a6020526040902054600160881b900463ffffffff16610ffa5760405162461bcd60e51b815260206004820152600b60248201526a27379031b7b7b63237bbb760a91b6044820152606401610bb3565b611003336122e5565b60405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a2565b611038611e83565b606d80546001600160a01b0319166001600160a01b0383169081179091556040517f70f5968ed58c0d81c7a8bca106614e2ea19375c430722f9f0ad68de8fd74f97890600090a250565b6001600160a01b038082166000908152606a602052604081205490911680156110ab5780610bde565b5090919050565b60008062093a806110c963ffffffff851642614f38565b6110db90670de0b6b3a7640000614ec2565b6110e59190614e88565b90506729a2241af62c000081111561115657611119611114826fe1b1e5f90f944d6e1c9e66c000000000614e88565b6123d8565b6111269062989680614ec2565b91506658d15e17628000821061114c576111476658d15e1762800083614f38565b61114f565b60005b9150611162565b67010a741a4627800091505b50919050565b610ee13382612552565b61117e848484846125d2565b50505050565b61118c611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916905590519182527f6843d4fd51341f35652c364bbf7be3666eacfcfc9ebac9db41bd6e05c432165f9101610f98565b6111e5611e83565b6001600160a01b0381166000818152609e6020908152604091829020805460ff1916600117905590519182527f66a41827fb347195a5a3f3b9c5c921310e2879c6715d6f2dff54add1a2fc562e9101610f98565b337f000000000000000000000000861f12764780896fd783ea615dd55df0ff8657526001600160a01b0316146112a05760405162461bcd60e51b815260206004820152600c60248201526b139bdd081d995c9a599a595960a21b6044820152606401610bb3565b6001600160a01b0382166112c65760405162461bcd60e51b8152600401610bb390614da9565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906113568483612a4a565b9050801561117e5761117e84838386612b06565b610ee181612b93565b610ee181612c76565b6001600160a01b0381166000908152606b6020526040812054610ad290612175565b6001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152610ad2908390612a4a565b600054610100900460ff1680611447575060005460ff16155b6114635760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611485576000805461ffff19166101011790555b611490848484612d0d565b6114a2609c805460ff19166001179055565b60408051808201909152670de0b6b3a76400008082526000602090920191909152609d55801561117e576000805461ff001916905550505050565b600054610100900460ff16806114f6575060005460ff16155b6115125760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015611534576000805461ffff19166101011790555b61153f84848461142e565b801561117e576000805461ff001916905550505050565b611562828260006121da565b5050565b603554600090610ae590429063ffffffff16612d7f565b60004382106115ce5760405162461bcd60e51b815260206004820152601f60248201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e6564006044820152606401610bb3565b610ad2606c83611eeb565b6060610ae5603954611d68565b6001600160a01b0381166000908152606b60205260408120548015611659576001600160a01b0383166000908152606b60205260409020611628600183614f38565b8154811061163857611638614ff6565b600091825260209091200154600160201b90046001600160e01b031661165c565b60005b6001600160e01b03169392505050565b611562826000836121da565b611562826000806121da565b61168d33612d8e565b565b609c5460ff166116e15760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610bb3565b609c805460ff191690557f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b039081167f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2919091161461177f5760405162461bcd60e51b81526020600482015260136024820152724f6e6c7920666f722073616d6520706169727360681b6044820152606401610bb3565b6040516370a0823160e01b81523060048201526000907f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b0316906370a082319060240160206040518083038186803b1580156117e157600080fd5b505afa1580156117f5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118199190614c4a565b905061182430612d8e565b6040516370a0823160e01b81523060048201526000907f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b0316906370a082319060240160206040518083038186803b15801561188657600080fd5b505afa15801561189a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118be9190614c4a565b905060006118cc8383614f38565b90506000811161191e5760405162461bcd60e51b815260206004820152601760248201527f4d75737420636f6d706f756e6420736f6d657468696e670000000000000000006044820152606401610bb3565b61192a81600080612e6e565b5050609c805460ff1916600117905550565b611944613060565b6001600160a01b0316336001600160a01b0316146119a45760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79205265636f6c6c61746572616c69736174696f6e204d6f64756c65006044820152606401610bb3565b6119ac61221a565b609d546119d190600160801b90046001600160801b0316670de0b6b3a7640000614f10565b609d80546fffffffffffffffffffffffffffffffff19166001600160801b03929092169190911790556040516370a0823160e01b81523060048201526000907f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b0316906370a082319060240160206040518083038186803b158015611a5c57600080fd5b505afa158015611a70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a949190614c4a565b9050611b03611aa1613060565b609d54670de0b6b3a764000090611ac890600160801b90046001600160801b031685614ec2565b611ad29190614e88565b6001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd21691906120a6565b6040517f8551ab4a9801f4813e5fff4a88eb374487886333f7c4e0aa1ca928a24916d53c90600090a150565b600080611b3a61311a565b5092915050565b610ee181612d8e565b336000908152603a6020526040902054600160881b900463ffffffff16801580611bd057507f0000000000000000000000000000000000000000000000000000000000127500611bc37f00000000000000000000000000000000000000000000000000000000001baf806001600160801b038416614e20565b611bcd9190614e20565b42115b15611bfd57600080611be133610419565b9092509050611bf8611bf38284614e20565b612c76565b505050565b336000818152603a6020526040812054610ee192600160a81b9091046001600160581b0316916001906125d2565b60408051808201909152600080825260208201526001600160a01b0383166000908152606b60205260409020805463ffffffff8416908110611c6f57611c6f614ff6565b60009182526020918290206040805180820190915291015463ffffffff81168252600160201b90046001600160e01b0316918101919091529392505050565b6001600160a01b0382166000908152603660205260408120548190611cdc906001600160801b031684614f38565b905080611d135750506001600160a01b038216600090815260366020526040902054600160801b90046001600160801b0316610ad2565b6000611d2882611d228761139e565b90613228565b6001600160a01b038616600090815260366020526040902054909150611d5f908290600160801b90046001600160801b0316614e20565b95945050505050565b606060005b602081108015611d9b5750828160208110611d8a57611d8a614ff6565b1a60f81b6001600160f81b03191615155b15611db25780611daa81614f9b565b915050611d6d565b60008167ffffffffffffffff811115611dcd57611dcd61500c565b6040519080825280601f01601f191660200182016040528015611df7576020820181803683370190505b509050600091505b602082108015611e2d5750838260208110611e1c57611e1c614ff6565b1a60f81b6001600160f81b03191615155b15610bde57838260208110611e4457611e44614ff6565b1a60f81b818381518110611e5a57611e5a614ff6565b60200101906001600160f81b031916908160001a90535081611e7b81614f9b565b925050611dff565b611e8b61323d565b6001600160a01b0316336001600160a01b03161461168d5760405162461bcd60e51b815260206004820152601960248201527f4f6e6c7920676f7665726e6f722063616e2065786563757465000000000000006044820152606401610bb3565b8154600090815b81811015611f4f576000611f068284613298565b905084868281548110611f1b57611f1b614ff6565b60009182526020909120015463ffffffff161115611f3b57809250611f49565b611f46816001614e20565b91505b50611ef2565b8115611f925784611f61600184614f38565b81548110611f7157611f71614ff6565b600091825260209091200154600160201b90046001600160e01b0316611f95565b60005b6001600160e01b031695945050505050565b600080611fb261311a565b90925090508115611bf857611fc682612109565b603580546001600160601b0392909216600160a01b026001600160a01b03909216919091179055611ff681612175565b6035805463ffffffff92909216600160201b0267ffffffff00000000199092169190911790556001600160a01b03831615611bf857604051806040016040528061203f8461227c565b6001600160801b0316815260200161205f61205a8686611cae565b61227c565b6001600160801b039081169091526001600160a01b0385166000908152603660209081526040909120835193909101518216600160801b0292909116919091179055505050565b6040516001600160a01b038316602482015260448101829052611bf890849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526132ef565b60006001600160601b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b6064820152608401610bb3565b5090565b600063ffffffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b6064820152608401610bb3565b61220f7f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd26001600160a01b03163330866133c1565b611bf8838383612e6e565b609d546001600160801b0316670de0b6b3a76400001461168d5760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c79207768696c652066756c6c7920636f6c6c61746572616c69736564006044820152606401610bb3565b60006001600160801b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20316044820152663238206269747360c81b6064820152608401610bb3565b806122ef81611fa7565b6001600160a01b0382166123155760405162461bcd60e51b8152600401610bb390614da9565b600080612321846133f9565b9150915061233282602001516135e9565b6001600160a01b0385166000908152603a60205260408120805460ff93909316600160781b0260ff60781b1984168117825560a0860151939192916123869185916001600160581b03918216911617614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0384166000908152603a6020526040902080546001600160881b0316905561117e8482613682565b6000816123e757506000919050565b816001600160801b82106124005760809190911c9060401b5b600160401b82106124165760409190911c9060201b5b600160201b821061242c5760209190911c9060101b5b6201000082106124415760109190911c9060081b5b61010082106124555760089190911c9060041b5b601082106124685760049190911c9060021b5b600882106124745760011b5b60016124808286614e88565b61248a9083614e20565b901c9050600161249a8286614e88565b6124a49083614e20565b901c905060016124b48286614e88565b6124be9083614e20565b901c905060016124ce8286614e88565b6124d89083614e20565b901c905060016124e88286614e88565b6124f29083614e20565b901c905060016125028286614e88565b61250c9083614e20565b901c9050600161251c8286614e88565b6125269083614e20565b901c905060006125368286614e88565b90508082106125455780611d5f565b509392505050565b919050565b600061255d83611082565b9050600061256a8461139e565b6001600160a01b038581166000818152606a602052604080822080546001600160a01b031916898616908117909155905194955093928616927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a461117e828483613742565b6125da6138fe565b8361261d5760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b609d546001600160801b0316670de0b6b3a7640000146126b057612645338560006001613964565b609d5461266b908490670de0b6b3a764000090611ac8906001600160801b031688614ec2565b6040518481526001600160a01b0384169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a361117e565b336000908152603a6020908152604091829020825160c08101845290546001600160581b03808216835263ffffffff600160581b830481169484019490945260ff600160781b8304811695840195909552600160801b82049094166060830152600160881b810490921660808201819052600160a81b90920490921660a083015261275c907f00000000000000000000000000000000000000000000000000000000001baf8090614e20565b42116127a25760405162461bcd60e51b815260206004820152601560248201527424a729aaa32324a1a4a2a72a2fa1a7a7a62227aba760591b6044820152606401610bb3565b7f00000000000000000000000000000000000000000000000000000000001275007f00000000000000000000000000000000000000000000000000000000001baf80826080015163ffffffff166127f99190614e20565b6128039042614f38565b11156128515760405162461bcd60e51b815260206004820152601760248201527f554e5354414b455f57494e444f575f46494e49534845440000000000000000006044820152606401610bb3565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b8304811695840186905260ff600160781b8404811695850195909552600160801b83049094166060840152600160881b82049093166080830152600160a81b900490911660a082015291906128d8906110b2565b905060008561290d57670de0b6b3a76400006128f48382614e20565b6128fe908a614ec2565b6129089190614e88565b61290f565b875b9050600061292583670de0b6b3a7640000614e20565b61293783670de0b6b3a7640000614ec2565b6129419190614e88565b60a08601519091506001600160581b03168083111561299b5760405162461bcd60e51b8152602060048201526016602482015275115e18d959591cc81b585e081dda5d1a191c985dd85b60521b6044820152606401610bb3565b600087806129a857508184145b90506129b73385836000613964565b6129c96129c48486614f38565b613c93565b6129fd6001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2168b856120a6565b6040518b81526001600160a01b038b169033907f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb9060200160405180910390a35050505050505050505050565b6000606482606001516064612a5f9190614e38565b8351612a6e9160ff1690614ee1565b612a789190614e9c565b6001600160581b03169050606482604001516064612a969190614e38565b612aa39060ff1683614ec2565b612aad9190614e88565b90507f000000000000000000000000000000000000000000000000000000000000000015610ad2576001600160a01b0383166000908152603b602052604090205461271090612afc9083614ec2565b610bde9190614e88565b83612b1081611fa7565b6001600160a01b0385166000908152603a60209081526040909120805460ff60801b1916600160801b60ff861602179055840151612b4d906135e9565b6001600160a01b0386166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055612b8c8584613682565b5050505050565b80612b9d81611fa7565b6001600160a01b038216612bc35760405162461bcd60e51b8152600401610bb390614da9565b600080612bcf846133f9565b915091506000612be283602001516135e9565b9050826040015160ff168160ff161415612c3e5760405162461bcd60e51b815260206004820152601960248201527f4e6f7468696e6720776f72746820706f6b696e672068657265000000000000006044820152606401610bb3565b6001600160a01b0385166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055612b8c8583613682565b612c7f3361139e565b612ccb5760405162461bcd60e51b815260206004820152601b60248201527f494e56414c49445f42414c414e43455f4f4e5f434f4f4c444f574e00000000006044820152606401610bb3565b612cd53382613d1b565b60405181815233907f8a05f911d8ab7fc50fec37ef4ba7f9bfcb1a3c191c81dcd824ad0946c4e20d659060200160405180910390a250565b600054610100900460ff1680612d26575060005460ff16155b612d425760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015612d64576000805461ffff19166101011790555b612d6c613f03565b6038849055603983905561153f82613f6e565b60008183116111625782610bde565b33612d9881611fa7565b33600090815260366020526040902054600160801b90046001600160801b03168015612e655733600090815260366020526040902080546001600160801b03908116909155603454612e1b916001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd28116921690869085166133c1565b604080516001600160801b038316815290516001600160a01b0385169133917f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9181900360200190a35b611bf833614045565b612e7661221a565b612e7e6138fe565b82612ec15760405162461bcd60e51b81526020600482015260136024820152721253959053125117d6915493d7d05353d55395606a1b6044820152606401610bb3565b6001600160a01b03821615612eda57612eda3383612552565b336000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152908280612fd757506000826080015163ffffffff16118015612fd757507f00000000000000000000000000000000000000000000000000000000001275007f00000000000000000000000000000000000000000000000000000000001baf80836080015163ffffffff16612fca9190614e20565b612fd49190614e20565b42115b9050801561300b5760405133907f3e58f193f71ff8415ab4cc6ac25f60be9ba7dfae9eef13769586d089b7887e1a90600090a25b61301633868361426a565b604080518681526001600160a01b038616602082015233917f9f9e4044c5742cca66ca090b21552bac14645e68bad7a92364a9d9ff18111a1c910160405180910390a25050505050565b6040516385acd64160e01b81527f39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f60048201526000907f000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb36001600160a01b0316906385acd6419060240160206040518083038186803b1580156130e257600080fd5b505afa1580156130f6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ae59190614b24565b6000806000613127611566565b6040805160808101825260355463ffffffff8082168352600160201b820416602083018190526001600160601b03600160401b8304811694840194909452600160a01b90910490921660608201529192506000906131859084614f38565b9050806131a25750606001516001600160601b0316939092509050565b60008183604001516001600160601b03166131bd9190614ec2565b905060006131c9610aea565b90508015806131d6575081155b156131f357505050606001516001600160601b0316939092509050565b60006131ff838361458c565b90508085606001516001600160601b031661321a9190614e20565b989597509495505050505050565b6000610bde8383670de0b6b3a76400006145a1565b60007f000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb36001600160a01b0316630c340a246040518163ffffffff1660e01b815260040160206040518083038186803b1580156130e257600080fd5b600060026132a68184614fb6565b6132b1600286614fb6565b6132bb9190614e20565b6132c59190614e88565b6132d0600284614e88565b6132db600286614e88565b6132e59190614e20565b610bde9190614e20565b6000613344826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166145c09092919063ffffffff16565b805190915015611bf857808060200190518101906133629190614bdb565b611bf85760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610bb3565b6040516001600160a01b038085166024830152831660448201526064810182905261117e9085906323b872dd60e01b906084016120d2565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152506001600160a01b0381166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152906134bc8383612a4a565b604051639295ac5960e01b81526001600160a01b0385811660048301529192507f000000000000000000000000861f12764780896fd783ea615dd55df0ff86575290911690639295ac5990602401602060405180830381600087803b15801561352457600080fd5b505af1158015613538573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061355c9190614d3d565b6001600160a01b0384166000908152603a60205260409020805460ff92909216600160801b0260ff60801b199092169190911790557f0000000000000000000000000000000000000000000000000000000000000000156135e4576135c26127106145cf565b6001600160a01b0384166000908152603b6020526040902061ffff9190911690555b915091565b600063ffffffff82166135fe57506000919050565b600061361063ffffffff841642614f38565b90506277f8808110156136265750600092915050565b62eff10081101561363a5750601492915050565b6301dfe20081101561364f5750601e92915050565b6302cfd3008110156136645750602892915050565b6303bfc4008110156136795750603292915050565b50603c92915050565b6001600160a01b0382166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a0820152613712908490612a4a565b90508181111561372f57611bf88361372a8484614f38565b614632565b611bf88361373d8385614f38565b61467f565b816001600160a01b0316836001600160a01b0316141580156137645750600081115b15611bf8576001600160a01b038316156137f2576001600160a01b0383166000908152606b60205260408120819061379f906146cc856146d8565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72483836040516137e7929190918252602082015260400190565b60405180910390a250505b6001600160a01b0382161561387b576001600160a01b0382166000908152606b6020526040812081906138289061484f856146d8565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051613870929190918252602082015260400190565b60405180910390a250505b606d546001600160a01b031615611bf857606d546040516216c4a960ea1b81526001600160a01b03858116600483015284811660248301526044820184905290911690635b12a40090606401600060405180830381600087803b1580156138e157600080fd5b505af11580156138f5573d6000803e3d6000fd5b50505050505050565b33321461168d57336000908152609e602052604090205460ff1661168d5760405162461bcd60e51b815260206004820152601a60248201527f4e6f7420612077686974656c697374656420636f6e74726163740000000000006044820152606401610bb3565b8361396e81611fa7565b6001600160a01b0385166139c45760405162461bcd60e51b815260206004820152601d60248201527f45524332303a206275726e2066726f6d207a65726f20616464726573730000006044820152606401610bb3565b6000806139d0876133f9565b9150915060008260a0015183600001516139ea9190614e5d565b6001600160581b031690508415613a83576001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19169055613a2e8161485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613a748161485b565b6001600160581b031660a08401525b868360a001516001600160581b03161015613ae05760405162461bcd60e51b815260206004820152601c60248201527f45524332303a206275726e20616d6f756e74203e2062616c616e6365000000006044820152606401610bb3565b613ae98761485b565b6001600160a01b0389166000908152603a6020526040902080546001600160581b03600160a81b808304821694909403169092026001600160a81b039092169190911790558515613bb6576001600160a01b0388166000908152603a6020526040812080546001600160581b03600160a81b820481169391613b6d91859116614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0388166000908152603a6020526040902080546001600160881b031690555b6000613bc3600889614e88565b613bcd9083614f38565b6020850151613be29063ffffffff1642614f38565b613bec9190614ec2565b90506000613bfa8383614e88565b90506000613c0b610e338342614f38565b6001600160a01b038c166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150613c4b826135e9565b6001600160a01b038d166000908152603a60205260409020805460ff60781b1916600160781b60ff8416021790559050613c858c87613682565b505050505050505050505050565b69d3c21bcecceda10000008110613d015760405162461bcd60e51b815260206004820152602c60248201527f43616e6e6f74206e6f746966792077697468206d6f7265207468616e2061206d60448201526b696c6c696f6e20756e69747360a01b6064820152608401610bb3565b8060376000828254613d139190614e20565b909155505050565b81613d2581611fa7565b6001600160a01b038316613d4b5760405162461bcd60e51b8152600401610bb390614da9565b600080613d57856133f9565b9150915060008260a001518360000151613d719190614e5d565b9050600085118015613d8c5750806001600160581b03168511155b613dd85760405162461bcd60e51b815260206004820152601e60248201527f4d7573742063686f6f7365206265747765656e203020616e64203130302500006044820152606401610bb3565b613de583602001516135e9565b6001600160a01b0387166000908152603a60205260409020805460ff92909216600160781b0260ff60781b19909216919091179055613e238561485b565b613e2d9082614f4f565b6001600160a01b0387166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b0392909216919091179055613e7042612175565b6001600160a01b0387166000908152603a60205260409020805463ffffffff92909216600160881b0263ffffffff60881b19909216919091179055613eb48561485b565b6001600160a01b0387166000908152603a6020526040902080546001600160581b0392909216600160a81b026001600160a81b03909216919091179055613efb8683613682565b505050505050565b600054610100900460ff1680613f1c575060005460ff16155b613f385760405162461bcd60e51b8152600401610bb390614dd2565b600054610100900460ff16158015613f5a576000805461ffff19166101011790555b8015610ee1576000805461ff001916905550565b613f7781610b44565b604051633e50523160e01b81526001600160a01b037f000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd216600482015273fb73476911c5e84556a5bf953644b7ef50f6cbc590633e5052319060240160206040518083038186803b158015613fea57600080fd5b505af4158015613ffe573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140229190614b24565b603480546001600160a01b0319166001600160a01b039290921691909117905550565b604051639295ac5960e01b81526001600160a01b0382811660048301526000917f000000000000000000000000861f12764780896fd783ea615dd55df0ff86575290911690639295ac5990602401602060405180830381600087803b1580156140ad57600080fd5b505af11580156140c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e59190614d3d565b905060007f0000000000000000000000000000000000000000000000000000000000000000614115576000614134565b6001600160a01b0383166000908152603b602052604090205461271014155b6001600160a01b0384166000908152603a602052604090205490915060ff838116600160801b909204161415806141685750805b15611bf8576001600160a01b0383166000908152603a60209081526040808320815160c08101835290546001600160581b03808216835263ffffffff600160581b830481169584019590955260ff600160781b8304811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a08201526141fd908590612a4a565b6001600160a01b0385166000908152603a60205260409020805460ff60801b1916600160801b60ff871602179055905081156142605761423e6127106145cf565b6001600160a01b0385166000908152603b6020526040902061ffff9190911690555b61117e8482613682565b8261427481611fa7565b6001600160a01b0384166142ca5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610bb3565b6000806142d6866133f9565b9150915060008260a0015183600001516142f09190614e5d565b90506142fb8661485b565b83516143079190614e5d565b6001600160a01b0388166000908152603a6020526040902080546affffffffffffffffffffff19166001600160581b039290921691909117905584156143c65760a08301516001600160a01b0388166000908152603a60205260408120805490919061437d9084906001600160581b0316614e5d565b82546001600160581b039182166101009390930a9283029190920219909116179055506001600160a01b0387166000908152603a6020526040902080546001600160881b031690555b602083015163ffffffff1661449c576143de42612175565b6001600160a01b0388166000908152603a6020908152604091829020805463ffffffff60581b198116600160581b63ffffffff968716810291821793849055855160c0810187526001600160581b039384169284169290921782528304861693810193909352600160781b820460ff90811694840194909452600160801b82049093166060830152600160881b81049093166080820152600160a81b9092041660a082015261449490889061372a908290612a4a565b50505061117e565b6000816001600160581b0316846020015163ffffffff16426144be9190614f38565b6144c89190614ec2565b905060006144d7600289614e88565b6144ea906001600160581b038516614e20565b6144f49083614e88565b90506000614505610e338342614f38565b6001600160a01b038b166000908152603a60205260408120805463ffffffff60581b1916600160581b63ffffffff851602179055909150614545826135e9565b6001600160a01b038c166000908152603a60205260409020805460ff60781b1916600160781b60ff841602179055905061457f8b87613682565b5050505050505050505050565b600081612afc670de0b6b3a764000085614ec2565b6000816145ae8486614ec2565b6145b89190614e88565b949350505050565b60606145b884846000856148c3565b600061ffff8211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201526536206269747360d01b6064820152608401610bb3565b6040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562600083836149eb565b6040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3611562826000836149eb565b6000610bde8284614f38565b82546000908190801561472257856146f1600183614f38565b8154811061470157614701614ff6565b600091825260209091200154600160201b90046001600160e01b0316614725565b60005b6001600160e01b0316925061473e83858763ffffffff16565b915060008111801561477c57504386614758600184614f38565b8154811061476857614768614ff6565b60009182526020909120015463ffffffff16145b156147dc5761478a82614a51565b86614796600184614f38565b815481106147a6576147a6614ff6565b9060005260206000200160000160046101000a8154816001600160e01b0302191690836001600160e01b03160217905550614846565b8560405180604001604052806147f143612175565b63ffffffff16815260200161480585614a51565b6001600160e01b039081169091528254600181018455600093845260209384902083519490930151909116600160201b0263ffffffff909316929092179101555b50935093915050565b6000610bde8284614e20565b60006001600160581b038211156121715760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203860448201526538206269747360d01b6064820152608401610bb3565b6060824710156149245760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610bb3565b843b6149725760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610bb3565b600080866001600160a01b0316858760405161498e9190614d5a565b60006040518083038185875af1925050503d80600081146149cb576040519150601f19603f3d011682016040523d82523d6000602084013e6149d0565b606091505b50915091506149e0828286614aba565b979650505050505050565b6001600160a01b0383161580614a0857506001600160a01b038216155b15614a3657614a33606c6001600160a01b03841615614a295761484f614a2d565b6146cc5b836146d8565b50505b611bf8614a4284611082565b614a4b84611082565b83613742565b60006001600160e01b038211156121715760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b6064820152608401610bb3565b60608315614ac9575081610bde565b825115614ad95782518084602001fd5b8160405162461bcd60e51b8152600401610bb39190614d76565b803563ffffffff8116811461254d57600080fd5b600060208284031215614b1957600080fd5b8135610bde81615022565b600060208284031215614b3657600080fd5b8151610bde81615022565b60008060408385031215614b5457600080fd5b8235614b5f81615022565b946020939093013593505050565b60008060408385031215614b8057600080fd5b8235614b8b81615022565b9150614b9960208401614af3565b90509250929050565b60008060408385031215614bb557600080fd5b8235614bc081615022565b91506020830135614bd081615045565b809150509250929050565b600060208284031215614bed57600080fd5b8151610bde81615037565b600080600060608486031215614c0d57600080fd5b83359250602084013591506040840135614c2681615022565b809150509250925092565b600060208284031215614c4357600080fd5b5035919050565b600060208284031215614c5c57600080fd5b5051919050565b60008060408385031215614c7657600080fd5b823591506020830135614bd081615022565b60008060008060808587031215614c9e57600080fd5b843593506020850135614cb081615022565b92506040850135614cc081615037565b91506060850135614cd081615037565b939692955090935050565b60008060408385031215614cee57600080fd5b823591506020830135614bd081615037565b60008060408385031215614d1357600080fd5b50508035926020909101359150565b600060208284031215614d3457600080fd5b610bde82614af3565b600060208284031215614d4f57600080fd5b8151610bde81615045565b60008251614d6c818460208701614f6f565b9190910192915050565b6020815260008251806020840152614d95816040850160208701614f6f565b601f01601f19169190910160400192915050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b60008219821115614e3357614e33614fca565b500190565b600060ff821660ff84168060ff03821115614e5557614e55614fca565b019392505050565b60006001600160581b03808316818516808303821115614e7f57614e7f614fca565b01949350505050565b600082614e9757614e97614fe0565b500490565b60006001600160581b0380841680614eb657614eb6614fe0565b92169190910492915050565b6000816000190483118215151615614edc57614edc614fca565b500290565b60006001600160581b0380831681851681830481118215151615614f0757614f07614fca565b02949350505050565b60006001600160801b0383811690831681811015614f3057614f30614fca565b039392505050565b600082821015614f4a57614f4a614fca565b500390565b60006001600160581b0383811690831681811015614f3057614f30614fca565b60005b83811015614f8a578181015183820152602001614f72565b8381111561117e5750506000910152565b6000600019821415614faf57614faf614fca565b5060010190565b600082614fc557614fc5614fe0565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610ee157600080fd5b8015158114610ee157600080fd5b60ff81168114610ee157600080fdfea2646970667358221220155d7c66c220678cc50928f92b1daeec0245e02d64d88f549ddb0b05148db48464736f6c63430008060033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb3000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2000000000000000000000000861f12764780896fd783ea615dd55df0ff865752000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd200000000000000000000000000000000000000000000000000000000001baf800000000000000000000000000000000000000000000000000000000000127500
-----Decoded View---------------
Arg [0] : _nexus (address): 0xAFcE80b19A8cE13DEc0739a1aaB7A028d6845Eb3
Arg [1] : _rewardsToken (address): 0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2
Arg [2] : _questManager (address): 0x861f12764780896FD783eA615Dd55Df0FF865752
Arg [3] : _stakedToken (address): 0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2
Arg [4] : _cooldownSeconds (uint256): 1814400
Arg [5] : _unstakeWindow (uint256): 1209600
-----Encoded View---------------
6 Constructor Arguments found :
Arg [0] : 000000000000000000000000afce80b19a8ce13dec0739a1aab7a028d6845eb3
Arg [1] : 000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2
Arg [2] : 000000000000000000000000861f12764780896fd783ea615dd55df0ff865752
Arg [3] : 000000000000000000000000a3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2
Arg [4] : 00000000000000000000000000000000000000000000000000000000001baf80
Arg [5] : 0000000000000000000000000000000000000000000000000000000000127500
Loading...
Loading
Loading...
Loading
OVERVIEW
Logic contract for V2 vault that stakes MTANet Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.