Source Code
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
There are no matching entriesUpdate your filters to view other transactions | |||||||||
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
ZNDStaking
Compiler Version
v0.8.26+commit.8a97fa7a
Optimization Enabled:
Yes with 10000 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {IZNDStaking} from "./IZNDStaking.sol";
import {ZNDTreasury} from "./ZNDTreasury.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/**
* @notice Implements staking and withdrawal functionality for central platform and externally owned accounts.
* @notice Implements rewards distribution functionality.
* @notice EOA interaction require implementation of EIP-712 for signing messages.
*/
contract ZNDStaking is ZNDTreasury, IZNDStaking, EIP712 {
using SafeERC20 for IERC20;
using ECDSA for bytes32;
using EnumerableSet for EnumerableSet.AddressSet;
///////////////////////////
// EIP712 Typehashes //
///////////////////////////
bytes32 private immutable _STAKE_EOA_TYPEHASH =
keccak256("StakeEOA(uint256 plan,uint256 amount)");
bytes32 private immutable _WITHDRAW_STAKE_EOA_TYPEHASH =
keccak256("WithdrawStakeEOA(uint256 stakeId,uint256 amount)");
bytes32 private immutable _WITHDRAW_REWARDS_EOA_TYPEHASH =
keccak256("WithdrawRewardsEOA(uint256 amount)");
////////////////////////////
// State Constants //
////////////////////////////
/// @dev Number of tiers in the Tier enum
uint256 private constant NUM_TIERS = 6;
/// @dev Number of plans in the Plan enum
uint256 private constant NUM_PLANS = 4;
/// @dev Number of Pools in total
uint256 private constant NUM_POOLS = NUM_PLANS * NUM_TIERS;
/// @dev Flag value to represent false for uint256
uint256 private constant FALSE = 1;
/// @dev Flag value to represent true for uint256
uint256 private constant TRUE = 2;
/// @dev Max number of logged days of a pool to be processed in a single batch
uint256 private constant BATCH_DAY_COUNT = 2 * (365 + 1);
////////////////////////////
// State Variables //
////////////////////////////
/// @dev Addresses of stakeholders stored
EnumerableSet.AddressSet private s_stakeholders;
/// @dev Stores the amount each stakeholder has stakes in each pool
mapping(address => uint256[NUM_POOLS])
private s_stakedPerStakeholderAndPool;
/// @dev How much each stakeholder has staked in total
mapping(address => uint256) private s_stakedOf;
/// @dev How many logs already exist for each pool
uint256[NUM_POOLS] private s_existingPoolLogCount;
/// @dev Stakes stored
mapping(uint256 => Stake) private s_stakes;
/// @dev Incremental counter of stakes, applied as stakeId when creating new stake
uint256 public s_nextStakeId;
/// @dev total number of staked ZND tokens
uint256 public s_totalStaked;
/// @dev a flag to make the start of reward computation callable only once ever
uint256 private s_rewardComputationStarted;
/// @dev timestamp at which the next reward can be computed
uint256 private s_nextValidRewardComputationTime;
/// @dev staked per tier and plan
uint256[NUM_POOLS] private s_stakedPerPool;
/// @dev Collected pool logs since last comprehensive reward distribution for each pool
mapping(uint256 => PoolLog[]) private s_poolLogs;
/// @dev Timestamp at which the last pool log was created
uint256 private s_lastPoolLogCreationTime;
/// @dev Amount of ZND tokens rewarded per stakeholder
mapping(address => Reward) private s_rewardOf;
/// @dev Number of minutes to move reset point
uint256 private s_resetPointOffset;
/// @dev Amount of rewards for daily distribution
uint256 public s_dailyReward;
///////////////////////////////
// Constructor //
///////////////////////////////
constructor(
address _zndPlatform,
address _discretionaryWithdrawalAccount,
address _overLimitWithdrawalAccount,
uint256 _initialDailyReward,
uint256 _initialDiscretionaryPoolBalance,
uint256 _initialRewardsPoolBalance,
VestingPayload[] memory _vestingPayload
)
ZNDTreasury(
_zndPlatform,
_discretionaryWithdrawalAccount,
_overLimitWithdrawalAccount,
_initialDiscretionaryPoolBalance,
_initialRewardsPoolBalance,
_vestingPayload
)
EIP712("ZNDStaking", "1")
{
if (_zndPlatform == address(0)) revert Znd_ZeroAddress();
s_zndPlatform = _zndPlatform;
s_nextStakeId = 1; // stakeIds starts from 1 to avoid mistaking for nonexistent value
s_totalStaked = 0;
s_dailyReward = _initialDailyReward;
s_resetPointOffset = 0;
s_rewardComputationStarted = FALSE;
}
////////////////////////////////////////
// Staking and Withdrawal //
////////////////////////////////////////
/// @inheritdoc IZNDStaking
function stakeCentral(
StakePayload[] calldata _stakes
) external onlyCentralPlatform returns (bool) {
uint256 numberOfStakes = _stakes.length;
// check if there's anything to stake
if (numberOfStakes == 0) revert Stake_NoStakesProvided();
// check for empty stakes
for (uint256 i = 0; i < numberOfStakes; i++) {
if (_stakes[i].amount == 0)
revert Stake_ZeroAmountStakeNotAllowed();
}
distributeRewards();
// if all the pool logs haven't been processed, return
if (
s_stakeholders.contains(msg.sender) &&
s_lastPoolLogCreationTime >
s_rewardOf[msg.sender].lastDistributionTime
) {
emit DidNotDistributeAllRewardsToSelf(msg.sender);
return false;
}
// set up local variables to track the staked amount and stake ids
uint256 stakedInBatch = 0;
uint256[] memory createdStakeIds = new uint256[](numberOfStakes);
// loop through the array, stake tokens, and track stake ids and total staked amount
for (uint256 i = 0; i < numberOfStakes; i++) {
StakePayload calldata stake = _stakes[i];
stakedInBatch += stake.amount;
createdStakeIds[i] = _stakeTokens(stake, i);
}
// set next stake id
s_nextStakeId += numberOfStakes;
// increase total staked amount
s_totalStaked += stakedInBatch;
emit StakedCentral(createdStakeIds, _stakes.length, stakedInBatch);
// transfer funds from central platform to this contract
IERC20(s_zndToken).safeTransferFrom(
msg.sender,
address(this),
stakedInBatch
);
return true;
}
/// @inheritdoc IZNDStaking
function stakeEOA(
uint256 _plan,
uint256 _amount,
bytes calldata _signature
) external onlyEOA returns (bool) {
// revert if nothing to stake
if (_amount == 0) revert Stake_ZeroAmountStakeNotAllowed();
// check if the message has been signed by the message sender
bytes32 digest = _hashTypedDataV4(
keccak256(abi.encode(_STAKE_EOA_TYPEHASH, _plan, _amount))
);
address signer = digest.recover(_signature);
if (signer != msg.sender) revert Znd_InvalidSigner();
// calls compute rewards distribution to take into account changes from last computation until now
// that way the consistency of the system state and rewards distribution is preserved
distributeRewards();
// if all the pool logs haven't been processed, return
if (
s_stakeholders.contains(msg.sender) &&
s_lastPoolLogCreationTime >
s_rewardOf[msg.sender].lastDistributionTime
) {
emit DidNotDistributeAllRewardsToSelf(msg.sender);
return false;
}
// the stake will be placed in one of 4 basic tier pools according to specified plan
Pool memory pool = Pool({tier: Tier.Basic, plan: Plan(_plan)});
// stake tokens
StakePayload memory stakePayload = StakePayload({
amount: _amount,
tier: uint256(pool.tier),
plan: uint256(pool.plan)
});
uint256 stakeId = _stakeTokens(stakePayload, 0);
// increase total staked amount
s_totalStaked += _amount;
// set next stake id
s_nextStakeId += 1;
emit StakedEOA(stakeId, msg.sender, _amount, Plan(_plan));
// transfer tokens from the EOA to this contract
IERC20(s_zndToken).safeTransferFrom(msg.sender, address(this), _amount);
return true;
}
/// @inheritdoc IZNDStaking
function withdrawStakesCentral(
WithdrawCentralPayload[] calldata _withdrawals
) external onlyCentralPlatform returns (bool) {
uint256 numberOfWithdrawals = _withdrawals.length;
// check if there's anything in the provided array and revert if not
if (numberOfWithdrawals == 0) revert Stake_NoWithdrawalsProvided();
// check for empty stakes
for (uint256 i = 0; i < numberOfWithdrawals; i++) {
if (_withdrawals[i].amount == 0)
revert Stake_ZeroAmountWithdrawalNotAllowed();
}
// calls compute rewards distribution to take into account changes from last computation until now
// that way the consistency of the system state and rewards distribution is preserved
distributeRewards();
// if all the pool logs haven't been processed, return
if (
s_stakeholders.contains(msg.sender) &&
s_lastPoolLogCreationTime >
s_rewardOf[msg.sender].lastDistributionTime
) {
emit DidNotDistributeAllRewardsToSelf(msg.sender);
return false;
}
// set up local variables that track ids of withdrawn stakes, withdrawn amount,
// and applied fees for early withdrawal
uint256[] memory stakeIds = new uint256[](numberOfWithdrawals);
uint256 penaltyFees = 0;
uint256 totalAmountToWithdraw = 0;
// loop through the provided array and execute withdrawals
for (uint256 i = 0; i < numberOfWithdrawals; i++) {
WithdrawCentralPayload calldata withdrawal = _withdrawals[i];
(uint256 amountToWithdraw, uint256 penaltyFee) = _withdrawTokens(
withdrawal.stakeId,
withdrawal.amount
);
stakeIds[i] = withdrawal.stakeId;
penaltyFees += penaltyFee;
totalAmountToWithdraw += amountToWithdraw;
}
// decrease total staked for the total amount to withdraw and applied penalty fees
s_totalStaked -= totalAmountToWithdraw + penaltyFees;
// increase balance of the fees pool
s_feesPoolBalance += penaltyFees;
emit WithdrawStakesCentral(
stakeIds,
stakeIds.length,
totalAmountToWithdraw,
penaltyFees
);
// transfer tokens to the central platform
IERC20(s_zndToken).safeTransfer(msg.sender, totalAmountToWithdraw);
return true;
}
/// @inheritdoc IZNDStaking
function withdrawStakeEOA(
uint256 _stakeId,
uint256 _amount,
bytes calldata _signature
) external onlyEOA returns (bool) {
// revert if nothing to stake
if (_amount == 0) revert Stake_ZeroAmountWithdrawalNotAllowed();
// check if the message has been signed by the message sender
bytes32 digest = _hashTypedDataV4(
keccak256(
abi.encode(_WITHDRAW_STAKE_EOA_TYPEHASH, _stakeId, _amount)
)
);
address signer = digest.recover(_signature);
if (signer != msg.sender) revert Znd_InvalidSigner();
// calls compute rewards distribution to take into account changes from last computation until now
// that way the consistency of the system state and rewards distribution is preserved
distributeRewards();
// if all the pool logs haven't been processed, return
if (
s_stakeholders.contains(msg.sender) &&
s_lastPoolLogCreationTime >
s_rewardOf[msg.sender].lastDistributionTime
) {
emit DidNotDistributeAllRewardsToSelf(msg.sender);
return false;
}
// withdraw tokens and collect how much to withdraw and applied penalty for early withdrawal
(uint256 amountToWithdraw, uint256 penaltyFee) = _withdrawTokens(
_stakeId,
_amount
);
// decrease total staked for the amount to withdraw and applied penalty fee
s_totalStaked -= amountToWithdraw + penaltyFee;
// increase balance of the fees pool
s_feesPoolBalance += penaltyFee;
emit WithdrawStakeEOA(
_stakeId,
msg.sender,
amountToWithdraw,
penaltyFee
);
// transfer tokens from this contract to the EOA
IERC20(s_zndToken).safeTransfer(msg.sender, amountToWithdraw);
return true;
}
////////////////////////////////////////////////////
// Rewards Computation and Withdrawal //
////////////////////////////////////////////////////
/// @inheritdoc IZNDStaking
function startRewardComputation() external onlyCentralPlatform {
if (TRUE == s_rewardComputationStarted)
revert Reward_ComputationAlreadyStarted();
s_rewardComputationStarted = TRUE;
s_nextValidRewardComputationTime = block.timestamp;
emit RewardComputationStarted();
}
/// @inheritdoc IZNDStaking
function withdrawRewardsEOA(
uint256 _amount,
bytes calldata _signature
) external onlyEOA {
// check if amount to withdraw is greater than 0
if (_amount == 0) revert Reward_ZeroAmountWithdrawalNotAllowed();
// check if the message has been signed by the message sender
bytes32 digest = _hashTypedDataV4(
keccak256(abi.encode(_WITHDRAW_REWARDS_EOA_TYPEHASH, _amount))
);
address signer = digest.recover(_signature);
if (signer != msg.sender) revert Znd_InvalidSigner();
distributeRewards();
// if all the pool logs haven't been processed, return
if (
s_stakeholders.contains(msg.sender) &&
s_lastPoolLogCreationTime >
s_rewardOf[msg.sender].lastDistributionTime
) {
emit DidNotDistributeAllRewardsToSelf(msg.sender);
return;
}
// check if the amount is not higher than available to caller
if (_amount > s_rewardOf[msg.sender].amount)
revert Reward_WithdrawInsufficientFunds();
// decrease amount of rewards caller posses
s_rewardOf[msg.sender].amount -= _amount;
// decrease amount of rewards available in the rewards pool
if (s_rewardsPoolBalance < _amount) {
revert Reward_InsufficientFundsInPool();
}
s_rewardsPoolBalance -= _amount;
emit WithdrawRewardsEOA(msg.sender, _amount);
// send tokens to the caller
IERC20(s_zndToken).safeTransfer(msg.sender, _amount);
}
/// @inheritdoc IZNDStaking
function withdrawRewardsCentral(
uint256 _amount
) external onlyCentralPlatform {
// check if amount to withdraw is greater than 0
if (_amount == 0) revert Reward_ZeroAmountWithdrawalNotAllowed();
distributeRewards();
// if all the pool logs haven't been processed, return
if (
s_stakeholders.contains(msg.sender) &&
s_lastPoolLogCreationTime >
s_rewardOf[msg.sender].lastDistributionTime
) {
emit DidNotDistributeAllRewardsToSelf(msg.sender);
return;
}
// check if the amount is not higher than available to caller
if (_amount > s_rewardOf[msg.sender].amount)
revert Reward_WithdrawInsufficientFunds();
// decrease amount of rewards caller posses
s_rewardOf[msg.sender].amount -= _amount;
// decrease amount of rewards available in the rewards pool
if (s_rewardsPoolBalance < _amount) {
revert Reward_InsufficientFundsInPool();
}
s_rewardsPoolBalance -= _amount;
emit WithdrawRewardsCentral(_amount);
// send tokens to the caller
IERC20(s_zndToken).safeTransfer(msg.sender, _amount);
}
/// @inheritdoc IZNDStaking
function areAllRewardsDistributed() external view returns (bool) {
return
s_rewardOf[msg.sender].lastDistributionTime >=
s_lastPoolLogCreationTime;
}
/**
* @notice Distributes all the uncollected rewards to caller based on pool logs.
* @dev It's being called internally by token balance affecting functionalities:
* @dev staking or withdrawal or tokens (central or EOA), reward withdrawals (central and EOA).
* @dev It's necessary to distribute all the rewards to self before proceeding with these functionalities.
* @dev If the caller has been inactive for extensive periods of time, which would be more
* @dev than 2 years of inactivity, the token balance affecting functionalities will emit
* @dev `DidNotDistributeAllRewardsToSelf` event, this means that this functionality has to be invoked explicitly
* @dev sufficient amount of times, by the stakeholder, in order for to collect all the rewards first.
* @dev Invoking it sufficient amount of times can be easily achieved by calling it in a loop until
* @dev `areAllRewardsDistributed` returns true, caller can then use the token balance affecting functionalities again.
*/
function distributeRewards() public {
// include all the new logs that happened in the meantime
_computeReward();
// if the caller is not a stakeholder return
if (!s_stakeholders.contains(msg.sender)) return;
// if the caller has already collected all of the rewards return
if (
s_rewardOf[msg.sender].lastDistributionTime >=
s_lastPoolLogCreationTime
) return;
// convert to local variable to save up on gas cost
uint256[NUM_POOLS]
memory stakedByStakeholderPerPool = s_stakedPerStakeholderAndPool[
msg.sender
];
// local variable to keep track of how much reward the stakeholder has collected
uint256 newlyCollectedRewards = 0;
// flag to know if the all the logs have been processed or not
// in order to know if the last reward distribution time should be modified
uint256 allLogsProcessed = TRUE;
// this loop will process at most `BATCH_DAY_COUNT` logs of each pool
// the stakeholder has funds in and collect the rewards for the processed logs
for (uint256 plan = 0; plan < NUM_PLANS; plan++) {
for (uint256 tier = 0; tier < NUM_TIERS; tier++) {
// calculates linearized index of pool
uint256 poolIndex = plan * NUM_TIERS + tier;
uint256 stakeholderFundsInPool = stakedByStakeholderPerPool[
poolIndex
];
// if stakeholder has nothing in pool, it will not affect reward
if (0 == stakeholderFundsInPool) continue;
// get the pool log range that should be processed
uint256 poolStartIdx = s_rewardOf[msg.sender].startIdxPerPool[
poolIndex
];
uint256 poolEndIdx = poolStartIdx + BATCH_DAY_COUNT;
if (poolEndIdx >= s_poolLogs[poolIndex].length) {
// if it is the last batch, prevent out of bounds access
poolEndIdx = s_poolLogs[poolIndex].length;
} else {
// otherwise not all logs can be processed so set the flag to false
allLogsProcessed = FALSE;
}
for (uint256 i = poolStartIdx; i < poolEndIdx; i++) {
// for the current pool log add the reward to the stakeholder based
// on the partition of his funds in the pool's total funds
newlyCollectedRewards +=
(stakeholderFundsInPool *
s_poolLogs[poolIndex][i].poolReward) /
s_poolLogs[poolIndex][i].stakedInPool;
}
// update the index of the next log to read to the log after current last
s_rewardOf[msg.sender].startIdxPerPool[poolIndex] = poolEndIdx;
}
}
// if all the logs have been processed
// update the last distribution time to the current time,
// otherwise do not update distribution time as there are
// more rewards to be distributed to the stakeholder
if (TRUE == allLogsProcessed) {
s_rewardOf[msg.sender].lastDistributionTime = block.timestamp;
}
// add the newly collected rewards to the stakeholders rewards
s_rewardOf[msg.sender].amount += newlyCollectedRewards;
// emit appropriate event
emit DistributedDailyReward(msg.sender);
}
/**
* @notice Distributes specific the uncollected rewards to caller based on pool logs, based on parameters
* @param _user the stakeholder for whom the rewards will be computed
* @param _plan the plan for whitch the reward will be computed
* @param _tier the tier for whitch the reward will be computed
* @param _noLogs the maximum number of logs to go through
* @dev It is very similar to the distributeRewards, with the difference being
* @dev that it only distributes a specific number of rewards for a specific user in a specific pool
*/
function distributeRewardsSpecific(
address _user,
uint256 _plan,
uint256 _tier,
uint256 _noLogs
) public {
// include all the new logs that happened in the meantime
_computeReward();
// if the caller is not a stakeholder return
if (!s_stakeholders.contains(_user)) return;
// if the caller has already collected all of the rewards return
if (s_rewardOf[_user].lastDistributionTime >= s_lastPoolLogCreationTime)
return;
// convert to local variable to save up on gas cost
uint256[NUM_POOLS]
memory stakedByStakeholderPerPool = s_stakedPerStakeholderAndPool[
_user
];
// local variable to keep track of how much reward the stakeholder has collected
uint256 newlyCollectedRewards = 0;
// calculates linearized index of pool
uint256 poolIndex = _plan * NUM_TIERS + _tier;
uint256 stakeholderFundsInPool = stakedByStakeholderPerPool[poolIndex];
// if stakeholder has nothing in pool, it will not affect reward
if (0 != stakeholderFundsInPool) {
// get the pool log range that should be processed
uint256 poolStartIdx = s_rewardOf[_user].startIdxPerPool[poolIndex];
uint256 poolEndIdx = poolStartIdx + _noLogs;
if (poolEndIdx >= s_poolLogs[poolIndex].length) {
// if it is the last batch, prevent out of bounds access
poolEndIdx = s_poolLogs[poolIndex].length;
}
for (uint256 i = poolStartIdx; i < poolEndIdx; i++) {
// for the current pool log add the reward to the stakeholder based
// on the partition of his funds in the pool's total funds
newlyCollectedRewards +=
(stakeholderFundsInPool *
s_poolLogs[poolIndex][i].poolReward) /
s_poolLogs[poolIndex][i].stakedInPool;
}
// update the index of the next log to read to the log after current last
s_rewardOf[_user].startIdxPerPool[poolIndex] = poolEndIdx;
}
// add the newly collected rewards to the stakeholders rewards
s_rewardOf[_user].amount += newlyCollectedRewards;
// emit appropriate event
emit DistributedDailyReward(_user);
}
/////////////////////////////////////
// Getters //
/////////////////////////////////////
/// @inheritdoc IZNDStaking
function getStake(uint256 _stakeId) external view returns (Stake memory) {
Stake storage stake = s_stakes[_stakeId];
if (stake.stakeholder == address(0)) revert Stake_DoesNotExist();
return stake;
}
/// @inheritdoc IZNDStaking
function getRewardAmount() external view returns (uint256) {
if (!s_stakeholders.contains(msg.sender))
revert Reward_NotAStakeholder();
return s_rewardOf[msg.sender].amount;
}
/// @inheritdoc IZNDStaking
function getRewardAmountOfAddress(
address _address
) external view onlyCentralPlatform returns (uint256) {
return s_rewardOf[_address].amount;
}
/// @inheritdoc IZNDStaking
function getAmountStakedInPool(
Tier _tier,
Plan _plan
) external view returns (uint256) {
return s_stakedPerPool[uint256(_plan) * NUM_TIERS + uint256(_tier)];
}
/////////////////////////////////////
// Setters //
/////////////////////////////////////
/// @inheritdoc IZNDStaking
function setDailyReward(uint256 _amount) external onlyCentralPlatform {
_computeReward();
s_dailyReward = _amount;
emit DailyRewardChanged(_amount);
}
/// @inheritdoc IZNDStaking
function setResetPointOffset(
uint256 _newOffset
) external onlyCentralPlatform {
if (s_totalStaked > 0) revert Parameters_PoolsAreNotEmpty();
s_resetPointOffset = _newOffset;
emit DailyRewardOffsetChanged(_newOffset);
}
//////////////////////////
// Internal Functions //
//////////////////////////
// Creates any missing logs for all the pools based on the token stakings and withdrawals
// that happened between the current block timestamp and `s_nextValidRewardComputationTime`.
function _computeReward() internal {
// if the computation has not been enabled by the platform, exit
if (FALSE == s_rewardComputationStarted) return;
// if already computed since the last reward computation reset, exit
if (s_nextValidRewardComputationTime > block.timestamp) return;
// if there is nothing to distribute, exit
if (s_dailyReward == 0) return;
// apply parameter changes if any have happened in the meantime
_checkPendingBoostChanges();
// calculates the number of rewards to be distributed,
// in case some days have been missed (because the function
// was not invoked), they will be calculated too
uint256 rewardsToDistributeCount = 1 +
(block.timestamp - s_nextValidRewardComputationTime) /
1 days;
// Calculates pool share and total share
// set up local variables to track the total share of tokens in all pools,
// share of tokens per pool, and reward per pool
uint256 totalShare = 0;
uint256[] memory poolShares = new uint256[](NUM_POOLS);
uint256[NUM_POOLS] memory stakedPerPool = s_stakedPerPool;
for (uint8 plan = 0; plan < NUM_PLANS; plan++) {
for (uint8 tier = 0; tier < NUM_TIERS; tier++) {
// calculates linearized index of pool
uint256 poolIndex = plan * NUM_TIERS + tier;
// if the pool is empty then the shares are also 0
if (0 == stakedPerPool[poolIndex]) continue;
// apply pool bonuses to the number of tokens
uint256 sharesInPool = (stakedPerPool[poolIndex] *
s_planBoost[plan] *
s_tierBoost[tier]) / 10000;
// save share calculation of the current pool
poolShares[poolIndex] = sharesInPool;
// add share of the current pool to the total share
totalShare += sharesInPool;
}
}
// if all shares are 0, there is no need to continue,
// as no stakes will result in no rewards, just update
// the timestamp for the next computation
if (totalShare == 0) {
// `block.timestamp - (block.timestamp % 1 days)` is midnight of the current day,
// eg: if now is 10:36 am, this will be 10 hours and 36 minutes earlier
// `+1 days` gets the next midnight, and then the offset is applied
s_nextValidRewardComputationTime =
block.timestamp -
(block.timestamp % 1 days) +
1 days +
s_resetPointOffset;
return;
}
// convert to a local variable to save up on gas costs
uint256 dailyReward = s_dailyReward;
// Log creation
// stores a new pool log for each pool
for (uint8 plan = 0; plan < NUM_PLANS; plan++) {
for (uint8 tier = 0; tier < NUM_TIERS; tier++) {
uint256 poolIndex = plan * NUM_TIERS + tier;
uint256 sharesInPool = poolShares[poolIndex];
// if the pool has no shares then its log will also contain
// no stakes and no reward, hence it can be skipped
if (sharesInPool == 0) continue;
PoolLog memory poolLog;
// for each pool calculate its reward based on the daily reward,
// number of rewards to be distributed and the participation of the
// current pool share in total share
poolLog.poolReward =
(dailyReward * rewardsToDistributeCount * sharesInPool) /
totalShare;
poolLog.stakedInPool = stakedPerPool[poolIndex];
// store the log
s_poolLogs[poolIndex].push(poolLog);
// increase the number of existing logs for the pool
s_existingPoolLogCount[poolIndex]++;
}
}
// store the new last log creation timestamp
s_lastPoolLogCreationTime = block.timestamp;
// math has already been explained in the same function a few lines above
s_nextValidRewardComputationTime =
block.timestamp -
(block.timestamp % 1 days) +
1 days +
s_resetPointOffset;
// emit appropriate event
emit ComputedDailyRewards();
}
function _stakeTokens(
StakePayload memory _payload,
uint256 _index
) internal returns (uint256 stakeId) {
// create appropriate pool
Pool memory pool = Pool({
tier: Tier(_payload.tier),
plan: Plan(_payload.plan)
});
// calculate stake id for the stake
stakeId = s_nextStakeId + _index;
// create and store Stake struct
s_stakes[stakeId] = Stake(
stakeId,
_payload.amount,
block.timestamp,
msg.sender,
pool
);
// increase the amount staked by the stakeholder in total
s_stakedOf[msg.sender] += _payload.amount;
// increase amount staked in appropriate pool
s_stakedPerPool[_poolToIndex(pool)] += _payload.amount;
// increase the amount staked by the stakeholder in appropriate pool
s_stakedPerStakeholderAndPool[msg.sender][
_poolToIndex(pool)
] += _payload.amount;
// add stakeholder to the set of stakeholder if not already present
if (s_stakeholders.add(msg.sender)) {
s_rewardOf[msg.sender].startIdxPerPool = s_existingPoolLogCount;
s_rewardOf[msg.sender].lastDistributionTime = block.timestamp;
}
return stakeId;
}
function _withdrawTokens(
uint256 _stakeId,
uint256 _amount
) internal returns (uint256 amountToWithdraw, uint256 penaltyFee) {
// Check if any changes to parametrs should take effect
_checkPendingPenaltyChanges();
// storage pointer to the stake specified by stake id
Stake storage stake = s_stakes[_stakeId];
// revert if stake doesn't exist
if (stake.stakeholder == address(0)) revert Stake_DoesNotExist();
// revert if try to withdraw more than staked
if (stake.amount < _amount) revert Stake_WithdrawInsufficientFunds();
// revert if message sender is not owner of the stake
if (msg.sender != stake.stakeholder) revert Stake_NotOwner();
// set up local variables to track amount to withdraw and penalty fees for early withdrawal
penaltyFee = 0;
amountToWithdraw = _amount;
// check if it's an early withdrawal
if (_shouldApplyPenalty(stake.stakedAt, stake.pool.plan)) {
// if yes apply penalty fee based on staking plan
penaltyFee =
(amountToWithdraw * s_penaltyFee[uint256(stake.pool.plan)]) /
10_000;
// lower the amount that will be withdrawn by penalty fee
amountToWithdraw -= penaltyFee;
}
// decrease the amount staked by the stakeholder in total
s_stakedOf[msg.sender] -= _amount;
// decrease the amount of tokens staked in the pool from which withdraw
s_stakedPerPool[_poolToIndex(stake.pool)] -= _amount;
// decrease the amount staked by the stakeholder in appropriate pool
s_stakedPerStakeholderAndPool[msg.sender][
_poolToIndex(stake.pool)
] -= _amount;
// if stakeholder does not have any funds staked anymore, remove the stakeholder
// wrapped in require because of static analysis
if (0 == s_stakedOf[msg.sender]) {
require(s_stakeholders.remove(msg.sender));
}
// decrease amount of staked in the stake that's withdrawn from
s_stakes[_stakeId].amount -= _amount;
// if staked amount in stake is dropped to 0
if (s_stakes[_stakeId].amount == 0) {
// and delete stake struct
delete s_stakes[_stakeId];
}
return (amountToWithdraw, penaltyFee);
}
// Function to check if a given timestamp is more than 'periodInDays' days ago.
// In that case penalty should be applied.
function _shouldApplyPenalty(
uint256 timestampToCheck,
Plan plan
) internal view returns (bool) {
uint256 currentTimestamp = block.timestamp;
uint256 periodInDays = s_planDuration[uint256(plan)];
uint256 daysAgoTimestamp = currentTimestamp - (periodInDays * 1 days);
return (timestampToCheck > daysAgoTimestamp);
}
function _poolToIndex(Pool memory pool) internal pure returns (uint256) {
return uint256(pool.plan) * NUM_TIERS + uint256(pool.tier);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)
pragma solidity ^0.8.20;
interface IERC5267 {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Burnable.sol)
pragma solidity ^0.8.20;
import {ERC20} from "../ERC20.sol";
import {Context} from "../../../utils/Context.sol";
/**
* @dev Extension of {ERC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract ERC20Burnable is Context, ERC20 {
/**
* @dev Destroys a `value` amount of tokens from the caller.
*
* See {ERC20-_burn}.
*/
function burn(uint256 value) public virtual {
_burn(_msgSender(), value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, deducting from
* the caller's allowance.
*
* See {ERC20-_burn} and {ERC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `value`.
*/
function burnFrom(address account, uint256 value) public virtual {
_spendAllowance(account, _msgSender(), value);
_burn(account, value);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol)
pragma solidity ^0.8.20;
import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";
/**
* @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Permit deadline has expired.
*/
error ERC2612ExpiredSignature(uint256 deadline);
/**
* @dev Mismatched signature.
*/
error ERC2612InvalidSigner(address signer, address owner);
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
constructor(string memory name) EIP712(name, "1") {}
/**
* @inheritdoc IERC20Permit
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
_approve(owner, spender, value);
}
/**
* @inheritdoc IERC20Permit
*/
function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
/**
* @inheritdoc IERC20Permit
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @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);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../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;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @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);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// 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 cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @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 or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* 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.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @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`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @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 ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile 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 {MessageHashUtils-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 tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
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.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile 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 {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError, bytes32) {
// 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 (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): 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.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @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) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*
* @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
abstract contract EIP712 is IERC5267 {
using ShortStrings for *;
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _cachedDomainSeparator;
uint256 private immutable _cachedChainId;
address private immutable _cachedThis;
bytes32 private immutable _hashedName;
bytes32 private immutable _hashedVersion;
ShortString private immutable _name;
ShortString private immutable _version;
string private _nameFallback;
string private _versionFallback;
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
_name = name.toShortStringWithFallback(_nameFallback);
_version = version.toShortStringWithFallback(_versionFallback);
_hashedName = keccak256(bytes(name));
_hashedVersion = keccak256(bytes(version));
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
_cachedThis = address(this);
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
return _cachedDomainSeparator;
} else {
return _buildDomainSeparator();
}
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/**
* @dev See {IERC-5267}.
*/
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: By default this function reads _name which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Name() internal view returns (string memory) {
return _name.toStringWithFallback(_nameFallback);
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: By default this function reads _version which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Version() internal view returns (string memory) {
return _version.toStringWithFallback(_versionFallback);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @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.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides tracking nonces for addresses. Nonces will only increment.
*/
abstract contract Nonces {
/**
* @dev The nonce used for an `account` is not the expected current nonce.
*/
error InvalidAccountNonce(address account, uint256 currentNonce);
mapping(address account => uint256) private _nonces;
/**
* @dev Returns the next unused nonce for an address.
*/
function nonces(address owner) public view virtual returns (uint256) {
return _nonces[owner];
}
/**
* @dev Consumes a nonce.
*
* Returns the current value and increments nonce.
*/
function _useNonce(address owner) internal virtual returns (uint256) {
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return _nonces[owner]++;
}
}
/**
* @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
*/
function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
uint256 current = _useNonce(owner);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
// | length | 0x BB |
type ShortString is bytes32;
/**
* @dev This library provides functions to convert short memory strings
* into a `ShortString` type that can be used as an immutable variable.
*
* Strings of arbitrary length can be optimized using this library if
* they are short enough (up to 31 bytes) by packing them with their
* length (1 byte) in a single EVM word (32 bytes). Additionally, a
* fallback mechanism can be used for every other case.
*
* Usage example:
*
* ```solidity
* contract Named {
* using ShortStrings for *;
*
* ShortString private immutable _name;
* string private _nameFallback;
*
* constructor(string memory contractName) {
* _name = contractName.toShortStringWithFallback(_nameFallback);
* }
*
* function name() external view returns (string memory) {
* return _name.toStringWithFallback(_nameFallback);
* }
* }
* ```
*/
library ShortStrings {
// Used as an identifier for strings longer than 31 bytes.
bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
error StringTooLong(string str);
error InvalidShortString();
/**
* @dev Encode a string of at most 31 chars into a `ShortString`.
*
* This will trigger a `StringTooLong` error is the input string is too long.
*/
function toShortString(string memory str) internal pure returns (ShortString) {
bytes memory bstr = bytes(str);
if (bstr.length > 31) {
revert StringTooLong(str);
}
return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
}
/**
* @dev Decode a `ShortString` back to a "normal" string.
*/
function toString(ShortString sstr) internal pure returns (string memory) {
uint256 len = byteLength(sstr);
// using `new string(len)` would work locally but is not memory safe.
string memory str = new string(32);
/// @solidity memory-safe-assembly
assembly {
mstore(str, len)
mstore(add(str, 0x20), sstr)
}
return str;
}
/**
* @dev Return the length of a `ShortString`.
*/
function byteLength(ShortString sstr) internal pure returns (uint256) {
uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
if (result > 31) {
revert InvalidShortString();
}
return result;
}
/**
* @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
*/
function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
if (bytes(value).length < 32) {
return toShortString(value);
} else {
StorageSlot.getStringSlot(store).value = value;
return ShortString.wrap(FALLBACK_SENTINEL);
}
}
/**
* @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
*/
function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return toString(value);
} else {
return store;
}
}
/**
* @dev Return the length of a string that was encoded to `ShortString` or written to storage using
* {setWithFallback}.
*
* WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
* actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
*/
function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return byteLength(value);
} else {
return bytes(store).length;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
/**
* @notice Common type definitions and errors.
*/
interface IZNDBase {
///////////////////////////
// Type Declarations //
///////////////////////////
/// @dev 6 possible tiers central platform users can achieve, external users belongs to basic tier
enum Tier {
Basic,
Silver,
Gold,
Platinum,
Diamond,
Ambassador
}
/// @dev 4 possible duration options for staking tokens
enum Plan {
ThirtyDays,
NinetyDays,
HundredEightyDays,
ThreeHundredSixtyDays
}
////////////////
// Errors //
////////////////
error Znd_NotEOA();
error Znd_NotZNDPlatform();
error Znd_ZeroAddress();
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import {IZNDBase} from "./IZNDBase.sol";
/**
* @notice Interface for staking parameters change functionality.
*/
interface IZNDParameters is IZNDBase {
///////////////////////////
// Type Declarations //
///////////////////////////
/// @dev Pending change to tier boost that still hasn't passed it's grace period
struct TierBoostChange {
/// @dev tier for which we are changing the boost amount
uint256 tier;
/// @dev the value to be changed to when the grace period ends
uint256 newValue;
/// @dev block timestamp of when the change was initiated
uint256 initiated;
}
// @dev Pending change to plan boost that still hasn't passed it's grace period
struct PlanBoostChange {
/// @dev plan for which we are changing the boost amount
uint256 plan;
/// @dev the value to be changed to when the grace period ends
uint256 newValue;
/// @dev block timestamp of when the change was initiated
uint256 initiated;
}
// @dev Pending change to penalty fee that still hasn't passed it's grace period
struct PenaltyChange {
/// @dev plan for which we are changing the penalty fee
uint256 plan;
/// @dev the value to be changed to when the grace period ends
uint256 newValue;
/// @dev block timestamp of when the change was initiated
uint256 initiated;
}
////////////////
// Errors //
////////////////
error Parameters_CooldownNotOver();
error Parameters_ChangeNotWithinBounds();
error Parameters_PlanOrTierNotValid();
////////////////
// Events //
////////////////
event ParameterChangeRequested(
string changeType,
uint256 planOrTier,
uint256 oldValue,
uint256 newValue
);
////////////////////////////////////
// Parameters Change //
////////////////////////////////////
/**
* @notice Initialise the change of tier boost to be set after the grace period
* @param _tier tier for which we are changing the boost
* @param _newValue the new value to be changed to
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
*/
function changeTierBoost(uint256 _tier, uint256 _newValue) external;
/**
* @notice Initialise the change of plan boost to be set after the grace period
* @param _plan plan for which we are changing the boost
* @param _newValue the new value to be changed to
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
*/
function changePlanBoost(uint256 _plan, uint256 _newValue) external;
/**
* @notice Initialise the change of penalty fee for a specific plan to be set after the grace period
* @param _plan plan for which we are changing the penalty fee
* @param _newValue the new value to be changed to
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
*/
function changePenaltyFee(uint256 _plan, uint256 _newValue) external;
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import {IZNDTreasury} from "./IZNDTreasury.sol";
/**
* @notice Interface for staking functionality.
*/
interface IZNDStaking is IZNDTreasury {
///////////////////////////
// Type Declarations //
///////////////////////////
/// @dev a pool that consists of tier and plan, there can be 24 of pools
struct Pool {
Tier tier;
Plan plan;
}
/// @dev payload that describes single stake by providing amount to be staked, tier and plan
/// @dev used only for central platform staking
struct StakePayload {
uint256 amount;
uint256 tier;
uint256 plan;
}
/// @dev payload that describes signle withdraw intention by providing stake ID and withdraw amount
/// @dev used only for central platform withdraw
struct WithdrawCentralPayload {
uint256 stakeId;
uint256 amount;
}
/// @dev a single stake stored in contract
struct Stake {
/// @dev incremental field that uniquely identify stake
uint256 stakeId;
/// @dev amount of tokens staked
uint256 amount;
/// @dev timestamp when tokens are staked
uint256 stakedAt;
/// @dev address that staked tokens, can be the central platform or an EOA
address stakeholder;
/// @dev a pool tokens are staked into
Pool pool;
}
/// @dev a single pool log stored after every computation
struct PoolLog {
/// @dev the amount of funds staked in pool at the time
uint256 stakedInPool;
/// @dev the amount of the reward the pool had at the time
uint256 poolReward;
}
/// @dev reward information stored for each stakeholder
struct Reward {
/// @dev total amount of the reward the stakeholder has
uint256 amount;
/// @dev timestamp at which the reward was last distributed to the stakeholder
uint256 lastDistributionTime;
/// @dev stores the start index from which the distribution is going to start for each pool
uint256[24] startIdxPerPool;
}
////////////////
// Errors //
////////////////
error Znd_InvalidSigner();
error Stake_ComprehensiveRewardDistributionOngoing();
error Stake_NoStakesProvided();
error Stake_NoWithdrawalsProvided();
error Stake_NotOwner();
error Stake_DoesNotExist();
error Stake_WithdrawInsufficientFunds();
error Stake_AddressNotFound();
error Stake_ZeroAmountStakeNotAllowed();
error Stake_ZeroAmountWithdrawalNotAllowed();
error Reward_WithdrawInsufficientFunds();
error Reward_ZeroAmountWithdrawalNotAllowed();
error Reward_ComputationAlreadyStarted();
error Reward_NotAStakeholder();
error Reward_InsufficientFundsInPool();
error Parameters_PoolsAreNotEmpty();
////////////////
// Events //
////////////////
event StakedCentral(
uint256[] stakeIds,
uint256 numberOfStakes,
uint256 totalAmount
);
event StakedEOA(
uint256 stakeId,
address indexed stakeholder,
uint256 amount,
Plan plan
);
event RewardComputationStarted();
event ComputedDailyRewards();
event DistributedDailyReward(address indexed recipient);
event BatchSizeChanged(uint256 newSize);
event FinishedComprehensiveRewardDistribution();
event WithdrawStakesCentral(
uint256[] stakeIds,
uint256 numberOfWithdrawals,
uint256 totalAmount,
uint256 totalPenalites
);
event WithdrawStakeEOA(
uint256 indexed stakeId,
address indexed stakeholder,
uint256 amountWithdrawn,
uint256 penatlyFee
);
event WithdrawRewardsCentral(uint256 amount);
event WithdrawRewardsEOA(
address indexed stakeholder,
uint256 amountWithdrawn
);
event DiscretionaryPoolWithdrawal(
address account,
uint256 amount,
uint256 limit
);
event DailyRewardOffsetChanged(uint256 newValue);
event DailyRewardChanged(uint256 amount);
event DidNotDistributeAllRewardsToSelf(address indexed stakeholder);
////////////////////////////////////////
// Staking and Withdrawal //
////////////////////////////////////////
/**
* @notice Allows the centralized platform to create a batch of stakes in a single transaction.
* @param _stakes array of StakePayload structs.
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
* @dev Requires all amounts to be greater than 0, reverts with Stake_ZeroAmountStakeNotAllowed error otherwise.
* @dev Requires that input array contains at least one element, reverts with Stake_NoStakesProvided error otherwise.
* @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
* @dev Emits StakedCentral event.
* @dev Emits DidNotDistributeAllRewardsToSelf event.
* @dev Transfers total amount of staked tokens from central platform to staking contract.
* @dev Returns true if staking was successful and false if it failed
*/
function stakeCentral(
StakePayload[] calldata _stakes
) external returns (bool);
/**
* @notice Allows an EOA to create a stake in a Basic tier, with selected plan.
* @param _plan Plan to be used for the stake.
* @param _amount Amount of tokens to be staked.
* @param _signature signature to validate the caller of the action and the validity of data.
* @dev Requires to be called by EOA, reverts with Znd_NotEOA error otherwise.
* @dev Requires amount to be greater than 0, reverts with Stake_ZeroAmountStakeNotAllowed error otherwise.
* @dev Requires to match signature with message sender, reverts with Znd_InvalidSigner error otherwise.
* @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
* @dev Emits StakedCentral event.
* @dev Emits DidNotDistributeAllRewardsToSelf event.
* @dev Transfers specified amount of tokens from EOA to staking contract.
* @dev Returns true if staking was successful and false if it failed
*/
function stakeEOA(
uint256 _plan,
uint256 _amount,
bytes calldata _signature
) external returns (bool);
/**
* @notice Allows the centralized platform to create a batch of withdrawals in a single transaction.
* @param _withdrawals array of WithdrawCentralPayload objects describing the stakes to be withdrawn.
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
* @dev Requires all amounts to be greater than 0, reverts with Stake_ZeroAmountWithdrawalNotAllowed error otherwise.
* @dev Requires that input array contains at least one element, reverts with Stake_NoWithdrawalsProvided error otherwise.
* @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
* @dev Emits WithdrawStakesCentral event.
* @dev Emits DidNotDistributeAllRewardsToSelf event.
* @dev Transfers total amount of withdrawn tokens from staking contract to central platform.
* @dev Returns true if withdrawing was successful and false if it failed
*/
function withdrawStakesCentral(
WithdrawCentralPayload[] calldata _withdrawals
) external returns (bool);
/**
* @notice Allows an EOA to withdraw funds from a stake up to staked amount.
* @param _stakeId ID of stake to be withdrawn.
* @param _amount amount of ZND tokens to withdraw.
* @param _signature signature to validate the caller of the action and the validity of data.
* @dev Requires to be called by EOA, reverts with Znd_NotEOA error otherwise.
* @dev Requires amount to be greater than 0, reverts with Stake_ZeroAmountWithdrawalNotAllowed error otherwise.
* @dev Requires to match signature with message sender, reverts with Znd_InvalidSigner error otherwise.
* @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
* @dev Emits WithdrawStakeEOA event.
* @dev Emits DidNotDistributeAllRewardsToSelf event.
* @dev Transfers specified amount of tokens from staking contract to the EOA.
* @dev Returns true if withdrawing was successful and false if it failed
*/
function withdrawStakeEOA(
uint256 _stakeId,
uint256 _amount,
bytes calldata _signature
) external returns (bool);
////////////////////////////////////////////////////
// Rewards Distribution and Withdrawal //
////////////////////////////////////////////////////
/**
* @notice Allows the centralized platform to mark the start of the reward computations.
* @dev Requires that the caller is the central platform, will revert with Reward_NotCentralPlatform otherwise.
* @dev Requires that the distribution has not already been started, will revert with Reward_ComputationAlreadyStarted otherwise.
*/
function startRewardComputation() external;
/**
* @notice Allows an EOA to withdraw earned rewards up to the amount earned.
* @param _amount amount of rewarded ZND tokens to withdraw.
* @param _signature signature to validate the caller of the action and the validity of data.
* @dev Requires to be called by an EOA, reverts with Znd_NotEOA error otherwise.
* @dev Requires amount to be greater than 0, reverts with Reward_ZeroAmountWithdrawalNotAllowed error otherwise.
* @dev Requires amount to be less or equal to rewards earned, reverts with Reward_WithdrawInsufficientFunds error otherwise.
* @dev Requires to match signature with message sender, reverts with Znd_InvalidSigner error otherwise.
* @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
* @dev Emits WithdrawRewardsEOA event.
* @dev Emits DidNotDistributeAllRewardsToSelf event.
* @dev Transfers requested amount of tokens from staking contract to the EOA.
*/
function withdrawRewardsEOA(
uint256 _amount,
bytes calldata _signature
) external;
/**
* @notice Allows the centralized platform to withdraw all rewards earned by central platform users.
* @notice It will further distribute appropriate amounts based on user stakes.
* @param _amount amount of rewarded ZND tokens to withdraw.
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
* @dev Requires amount to be greater than 0, reverts with Reward_ZeroAmountWithdrawalNotAllowed error otherwise.
* @dev Requires amount to be less or equal to rewards earned, reverts with Reward_WithdrawInsufficientFunds error otherwise.
* @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
* @dev Emits WithdrawRewardsCentral event.
* @dev Emits DidNotDistributeAllRewardsToSelf event.
* @dev Transfers requested amount of tokens from staking contract to central platform.
*/
function withdrawRewardsCentral(uint256 _amount) external;
/**
* @notice Returns a boolean that says if there are more rewards to distribute to the caller.
* @dev If event `DidNotDistributeAllRewardsToSelf` has been emitted - after staking,
* @dev withdrawing stake, or withdrawing reward (token balance affecting functionalities),
* @dev the caller of the function is supposed to invoke this function in a loop until it returns true.
* @dev Once all the rewards have been distributed, user can then use token balance affecting functionalities.
*/
function areAllRewardsDistributed() external returns (bool);
/////////////////////////////////////
// Setters //
/////////////////////////////////////
/**
* @notice Set the amount of rewards to be distributed daily.
* @param _amount amount of rewards to be distributed daily.
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
*/
function setDailyReward(uint256 _amount) external;
/**
* @notice Sets new offset (from UTC midnight) for reset point for reward computation.
* @param _newOffset number of minutes to move reset point from UTC midnight.
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
* @dev Requires all pools to be empty at the time of the offset change, reverts with Parameters_PoolAreNotEmpty error otherwise.
*/
function setResetPointOffset(uint256 _newOffset) external;
/////////////////////////////////////
// Getters //
/////////////////////////////////////
/**
* @notice Fetches a stake by its id.
* @param _stakeId id of the stake.
* @dev Requires stake to exist, otherwise revert with Stake_DoesNotExist error.
* @dev Requires message sender to be stake owner, otherwise revert with Stake_NotOwner error.
* @return Stake entity that represents the stake.
*/
function getStake(uint256 _stakeId) external view returns (Stake memory);
/**
* @notice Gets amount of ZND tokens awarded to the caller.
* @dev Requires to be called by an actual stakeholder, reverts with Reward_NotAStakeholder error otherwise.
* @return amout of ZND tokens awarded to the caller address.
*/
function getRewardAmount() external view returns (uint256);
/**
* @notice Gets amount of ZND tokens awarded to specified address.
* @param _address address to get reward of.
* @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
* @return amount of ZND tokens awarded to specified address.
*/
function getRewardAmountOfAddress(
address _address
) external view returns (uint256);
/**
* @notice Gets amount of ZND tokens staked in chosen pool.
* @param _tier tier of the pool.
* @param _plan plan of the pool.
* @return amount of ZND tokens staked in the specified pool.
*/
function getAmountStakedInPool(
Tier _tier,
Plan _plan
) external view returns (uint256);
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import {IZNDParameters} from "./IZNDParameters.sol";
/**
* @notice Interface for treasury functionality.
*/
interface IZNDTreasury is IZNDParameters {
///////////////////////////
// Type Declarations //
///////////////////////////
enum TreasuryPool {
RewardsPool,
DiscretionaryPool,
FeesPool,
VestingPool
}
/// @dev payload that describes vesting policy for a recipient.
/// @dev every vesting, after the first one, is unlocked exactly 30 days after the previous one.
struct VestingPayload {
/// @dev wallet that is to receive the vesting
address recipient;
/// @dev the amount of funds to be unlocked over time
uint256 quantityToUnlockOverTime;
/// @dev the number of days over which the total amount will be unlocked
uint256 numberOfDays;
/// @dev time that needs to pass, since deployment, in order for first vesting to be unlocked
uint256 cliffPeriodEnd;
/// @dev the amount of funds to be unlocked initially
uint256 initialUnlock;
}
/// @dev vesting data for a single recipient.
struct VestingData {
/// @dev the amount of unlocked available to be requested
uint256 availableTokens;
/// @dev the amount of times the recepient will receive their vesting
uint256 numberOfUnlocksLeft;
/// @dev the amount that is unlocked in a single vesting
uint256 amountToReceivePerUnlock;
/// @dev last time a vesting has been unlocked
uint256 nextUnlockTimestamp;
}
////////////////
// Errors //
////////////////
error Treasury__InvalidAddress();
error Treasury__NotTreasuryAccount();
error Treasury__OverLimitWithdrawalNotAllowed();
error Treasury__AmountNotSpecified();
error Treasury__InsufficientFunds();
error Treasury__WithdrawalAccountCanNotBeOwner();
error Treasury__WithdrawalAccountCanNotBeSpecialAccount();
error Treasury__SpecialAccountCanNotBeOwner();
error Treasury__SpecialAccountCanNotBeVestingRecipient();
error Treasury__OwnerAccountCanNotBeVestingRecipient();
error Treasury__RenouncingOwnershipIsDisabled();
error Treasury__QuantityUnlockOverTimeNotDivisibleByNumberOfDays();
error Treasury__DuplicateVestingAddress();
////////////////
// Events //
////////////////
event Treasury__Funding(
address indexed funder,
uint256 amount,
TreasuryPool pool
);
event Treasury__Withdrawal(
address indexed withdrawAccount,
address indexed withdrawTo,
uint256 amount,
TreasuryPool pool
);
event Treasury__WithdrawalAccountUpdated(
address indexed updater,
address indexed newAccount
);
event Treasury__SpecialAccountUpdated(
address indexed updater,
address indexed newAccount
);
event Treasury__WithdrawalLimitUpdated(
address indexed updater,
uint256 newLimit
);
event Treasury__VestingPayoutCompleted(
address indexed recipient,
uint256 amount
);
/////////////////
// Funding //
/////////////////
/**
* @notice Send ZND tokens to rewards pool.
* @param _amount the amount of tokens to add to the pool.
* @dev emits Treasury__Funding event.
*/
function fundRewardsPool(uint256 _amount) external;
/**
* @notice Send ZND tokens to discretionary pool.
* @param _amount the amount of tokens to add to the pool.
* @dev emits Treasury__Funding event.
*/
function fundDiscretionaryPool(uint256 _amount) external;
///////////////////////////////////////////////////////
// Withdrawals from Discretionary and Fees pools //
///////////////////////////////////////////////////////
/**
* @notice Withdraw ZND tokens from discretionary pool to provided account.
* @param _to the address where to send tokens.
* @param _amount the amount of tokens to withdraw from the pool.
* @dev requires to be called by owner, regular account, or special account, reverts with Treasury__NotTreasuryAccount error otherwise.
* @dev requires amount to be greater than 0, reverts with Treasury__AmountNotSpecified error otherwise.
* @dev requires amount to be less than discretionary pool balance, reverts with Treasury__InsufficientFunds error otherwise.
* @dev requires amount to be less or equal than withdrawal limit when withdraw with regular account,
* @dev reverts with Treasury__OverLimitWithdrawalNotAllowed error otherwise.
* @dev emits Treasury__Withdrawal event.
*/
function withdrawFromDiscretionaryPool(
address _to,
uint256 _amount
) external;
/**
* @notice Withdraw ZND tokens from fees pool to provided account.
* @param _to the address where to send tokens.
* @param _amount the amount of tokens to withdraw from the pool.
* @dev requires to be called by owner, regular account, or special account, reverts with Treasury__NotTreasuryAccount error otherwise.
* @dev requires amount to be greater than 0, reverts with Treasury__AmountNotSpecified error otherwise.
* @dev requires amount to be less than fees pool balance, reverts with Treasury__InsufficientFunds error otherwise.
* @dev requires amount to be less or equal than withdrawal limit when withdraw with regular account,
* @dev reverts with Treasury__OverLimitWithdrawalNotAllowed error otherwise.
* @dev emits Treasury__Withdrawal event.
*/
function withdrawFromFeesPool(address _to, uint256 _amount) external;
///////////////////////
// Vesting Payout //
///////////////////////
/**
* @notice Dispenses funds to the recipient if the recipient is qualify to receive them.
* @param _amount the amount of tokens to be dispensed to recipient.
* @dev Transfers the tokens to the recipient and updates the state, if the recipient is qualified to receive the payout.
* @dev The recipient qualifies to receive the requested amount if and only if sufficient amount of time has passed to
* @dev receive it and they have enough tokens available in their vesting to receive it.
* @dev Required to be called by a valid recipient address, reverts with Treasury__InvalidAddress error otherwise.
* @dev Required that the amount be less or equal to the amount available for address, reverts with Treasury__InsufficientFunds error otherwise.
* @dev Emits Treasury__VestingPayoutCompleted event.
* @return Flag that represents if requested payout was paid out or not.
*/
function requestPayout(uint256 _amount) external returns (bool);
/**
* @notice Returns the amount of tokens that can be claimed by the user.
* @param _address the adress of the user for who the claimable vesting amount is to be calculated.
*/
function getClaimableVestingAmount(
address _address
) external view returns (uint256);
////////////////
// Setters //
////////////////
/**
* @notice Set limit for withdrawals from discretionary pool.
* @param _limit maximal number of tokens that can be withdrawn through limited withdrawals.
* @dev requires caller to be owner of treasury, reverts with OwnableUnauthorizedAccount error otherwise.
* @dev emits Treasury__WithdrawalLimitUpdated event.
*/
function setWithdrawLimit(uint256 _limit) external;
/**
* @notice Set account address that can withdraw from discretionary pool up to defined limit.
* @param _account account address that can withdraw from discretionary pool up to defined limit.
* @dev requires caller to be owner of treasury, reverts with OwnableUnauthorizedAccount error otherwise.
* @dev requires account to be non zero address, reverts with Treasury__InvalidAddress error otherwise.
* @dev requires account not to be owner, reverts with Treasury__WithdrawalAccountCanNotBeOwner error otherwise.
* @dev requires account not to be special account, reverts with Treasury__WithdrawalAccountCanNotBeSpecialAccount error otherwise.
* @dev emits Treasury__WithdrawalAccountUpdated event.
*/
function setWithdrawalAccount(address _account) external;
/**
* @notice Set account address that can withdraw from discretionary pool over defined limit.
* @param _account account address that can withdraw from discretionary pool over defined limit.
* @dev requires caller to be owner of treasury, reverts with OwnableUnauthorizedAccount error otherwise.
* @dev requires account to be non zero address, reverts with Treasury__InvalidAddress error otherwise.
* @dev requires account not to be owner, reverts with Treasury__SpecialAccountCanNotBeOwner error otherwise.
* @dev requires account not to be regular account, reverts with Treasury__WithdrawalAccountCanNotBeSpecialAccount error otherwise.
* @dev emits Treasury__SpecialAccountUpdated event.
*/
function setSpecialAccount(address _account) external;
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IZNDBase} from "./IZNDBase.sol";
import {ZNDToken} from "./ZNDToken.sol";
/**
* @notice Base contract that sets up common state variables and modifiers.
* @notice Deploys ZND token upon deployment and becomes owner of total token supply.
*/
contract ZNDBase is IZNDBase, Ownable2Step {
////////////////////////////
// State Variables //
////////////////////////////
/// @dev Token used for vesting, staking, rewards and fees
ZNDToken public immutable s_zndToken;
/// @dev ZND platform account that will have elevated privileges
address public immutable s_zndPlatform;
///////////////////////////////
// Modifiers //
///////////////////////////////
modifier onlyCentralPlatform() {
if (msg.sender != s_zndPlatform) revert Znd_NotZNDPlatform();
_;
}
modifier onlyEOA() {
if (msg.sender == s_zndPlatform)
revert Znd_NotEOA();
_;
}
///////////////////////////////
// Constructor //
///////////////////////////////
constructor(address _zndPlatform) Ownable(msg.sender) {
if (_zndPlatform == address(0)) revert Znd_ZeroAddress();
s_zndPlatform = _zndPlatform;
s_zndToken = new ZNDToken();
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;
import {IZNDParameters} from "./IZNDParameters.sol";
import {ZNDBase} from "./ZNDBase.sol";
/**
* @notice Implements staking parameters change functionality for payout boosts and penalty fees.
* @notice Parameters can be changed only within the predefined limits set up in constructor.
* @notice Parameter changes are reflected after grace period pass and can not be changed again until cool down period is over.
*/
contract ZNDParameters is ZNDBase, IZNDParameters {
////////////////////////////
// State Variables //
////////////////////////////
/// @dev Plan => duration in days
mapping(uint256 => uint256) public s_planDuration;
// Reward boost mappings
/// @dev Tier => Reward boost
mapping(uint256 => uint256) public s_tierBoost;
/// @dev Plan => Reward boost
mapping(uint256 => uint256) public s_planBoost;
/// @dev Plan => Penalty fee in bps
mapping(uint256 => uint256) public s_penaltyFee;
// Parameter change limitations
uint256 public immutable s_tierBoostLowerLimit;
uint256 public immutable s_tierBoostUpperLimit;
uint256 public immutable s_tierBoostStepLimit;
uint256 public immutable s_planBoostLowerLimit;
uint256 public immutable s_planBoostUpperLimit;
uint256 public immutable s_planBoostStepLimit;
uint256 public immutable s_penaltyFeeLowerLimit;
mapping(uint256 => uint256) public s_penaltyFeeUpperLimit;
uint256 public immutable s_penaltyFeeStepLimit;
uint256 public immutable s_parameterChangeGrace;
uint256 public immutable s_parameterChangeCooldown;
// Parameter change state variables
// These are all circular buffers, they will be used for storing pending changes to avoid unnecessary removal and
// addition of elements to a plain array while preserving the order of pending changes for the purpose of simplifying
// checking for ones that passed their grace period
// Just in case, explanation of how it works will be provided as they are used
TierBoostChange[6] internal s_tierBoostChanges;
PlanBoostChange[4] internal s_planBoostChanges;
PenaltyChange[4] internal s_penaltyChanges;
// Start and end indexes for the circular buffers
uint256 internal s_tbcIdxStart;
uint256 internal s_tbcIdxEnd;
uint256 internal s_pbcIdxStart;
uint256 internal s_pbcIdxEnd;
uint256 internal s_pcIdxStart;
uint256 internal s_pcIdxEnd;
// Timestamps for the last change of each parameter
mapping(uint256 => uint256) internal s_lastTierBoostChange;
mapping(uint256 => uint256) internal s_lastPlanBoostChange;
mapping(uint256 => uint256) internal s_lastPenaltyChange;
constructor(address _zndPlatform) ZNDBase(_zndPlatform) {
// Initialize reward boost as scaled integer values for each tier
s_tierBoost[uint256(Tier.Basic)] = 100; // 1x
s_tierBoost[uint256(Tier.Silver)] = 200; // 2x
s_tierBoost[uint256(Tier.Gold)] = 220; // 2.2x
s_tierBoost[uint256(Tier.Platinum)] = 240; // 2.4x
s_tierBoost[uint256(Tier.Diamond)] = 260; // 2.6x
s_tierBoost[uint256(Tier.Ambassador)] = 300; // 3x
// Initialize reward boost bps for each plan
s_planBoost[uint256(Plan.ThirtyDays)] = 100; // 1x
s_planBoost[uint256(Plan.NinetyDays)] = 150; // 1.5x
s_planBoost[uint256(Plan.HundredEightyDays)] = 200; // 2x
s_planBoost[uint256(Plan.ThreeHundredSixtyDays)] = 300; // 3x
// Initialize plan duration in days
s_planDuration[uint256(Plan.ThirtyDays)] = 30;
s_planDuration[uint256(Plan.NinetyDays)] = 90;
s_planDuration[uint256(Plan.HundredEightyDays)] = 180;
s_planDuration[uint256(Plan.ThreeHundredSixtyDays)] = 360;
// Initialize penalties bps for each plan
s_penaltyFee[uint256(Plan.ThirtyDays)] = 100; // 1%
s_penaltyFee[uint256(Plan.NinetyDays)] = 300; // 3%
s_penaltyFee[uint256(Plan.HundredEightyDays)] = 600; // 6%
s_penaltyFee[uint256(Plan.ThreeHundredSixtyDays)] = 1200; // 12%
// Initialise all parameter change limits
s_tierBoostLowerLimit = 100; // 1x
s_tierBoostUpperLimit = 1000; // 10x
s_tierBoostStepLimit = 1; // 0.01
s_planBoostLowerLimit = 100; // 1x
s_planBoostUpperLimit = 1000; // 10x
s_planBoostStepLimit = 1; // 0.01
s_penaltyFeeLowerLimit = 0; // 0%
s_penaltyFeeStepLimit = 1; // 0.01%
s_penaltyFeeUpperLimit[uint256(Plan.ThirtyDays)] = 500; // 5%
s_penaltyFeeUpperLimit[uint256(Plan.NinetyDays)] = 1000; // 10%
s_penaltyFeeUpperLimit[uint256(Plan.HundredEightyDays)] = 1500; // 15%
s_penaltyFeeUpperLimit[uint256(Plan.ThreeHundredSixtyDays)] = 2500; // 25%
s_parameterChangeGrace = 1;
s_parameterChangeCooldown = 3;
// Set initial change of parameters to contract creation
s_lastTierBoostChange[uint256(Tier.Basic)] = block.timestamp;
s_lastTierBoostChange[uint256(Tier.Silver)] = block.timestamp;
s_lastTierBoostChange[uint256(Tier.Gold)] = block.timestamp;
s_lastTierBoostChange[uint256(Tier.Platinum)] = block.timestamp;
s_lastTierBoostChange[uint256(Tier.Diamond)] = block.timestamp;
s_lastTierBoostChange[uint256(Tier.Ambassador)] = block.timestamp;
s_lastPlanBoostChange[uint256(Plan.ThirtyDays)] = block.timestamp;
s_lastPlanBoostChange[uint256(Plan.NinetyDays)] = block.timestamp;
s_lastPlanBoostChange[uint256(Plan.HundredEightyDays)] = block
.timestamp;
s_lastPlanBoostChange[uint256(Plan.ThreeHundredSixtyDays)] = block
.timestamp;
s_lastPenaltyChange[uint256(Plan.ThirtyDays)] = block.timestamp;
s_lastPenaltyChange[uint256(Plan.NinetyDays)] = block.timestamp;
s_lastPenaltyChange[uint256(Plan.HundredEightyDays)] = block.timestamp;
s_lastPenaltyChange[uint256(Plan.ThreeHundredSixtyDays)] = block
.timestamp;
// Set indexes for the circular buffer
s_tbcIdxStart = 0;
s_tbcIdxEnd = 0;
s_pbcIdxStart = 0;
s_pbcIdxEnd = 0;
s_pcIdxStart = 0;
s_pcIdxEnd = 0;
}
///////////////////////////////////
// External Functions //
///////////////////////////////////
function changeTierBoost(
uint256 _tier,
uint256 _newValue
) external onlyCentralPlatform {
// We check if the tier is valid
if (_tier >= 6) {
revert Parameters_PlanOrTierNotValid();
}
// We check if the cooldown period for the change has expired
if (
block.timestamp - s_lastTierBoostChange[_tier] <
30 days * s_parameterChangeCooldown
) revert Parameters_CooldownNotOver();
_checkPendingBoostChanges();
// Find the absolute value of the change difference
uint256 valDif = _newValue > s_tierBoost[_tier]
? _newValue - s_tierBoost[_tier]
: s_tierBoost[_tier] - _newValue;
// Check if the change is within limitations
if (
_newValue > s_tierBoostUpperLimit ||
_newValue < s_tierBoostLowerLimit ||
valDif > s_tierBoostStepLimit
) revert Parameters_ChangeNotWithinBounds();
// Save the timestamp of the current change as latest
s_lastTierBoostChange[_tier] = block.timestamp;
// Add the current change to the pending changes buffer
TierBoostChange memory newChange = TierBoostChange({
tier: _tier,
newValue: _newValue,
initiated: block.timestamp
});
s_tierBoostChanges[s_tbcIdxEnd] = newChange;
s_tbcIdxEnd = (s_tbcIdxEnd + 1) % 6; // As we are using a circular buffer, the indexing of the array "wraps around"
// back to the beginning of the array after adding to the end of the buffer
emit ParameterChangeRequested(
"TierBoost",
_tier,
s_tierBoost[_tier],
_newValue
);
}
function changePlanBoost(
uint256 _plan,
uint256 _newValue
) external onlyCentralPlatform {
// Check if tier is valid
if (_plan >= 4) {
revert Parameters_PlanOrTierNotValid();
}
// We check if the cooldown period for the change has expired
if (
block.timestamp - s_lastPlanBoostChange[_plan] <
s_parameterChangeCooldown * 30 days
) revert Parameters_CooldownNotOver();
_checkPendingBoostChanges();
// Find the absolute value of the change difference
uint256 valDif = _newValue > s_planBoost[_plan]
? _newValue - s_planBoost[_plan]
: s_planBoost[_plan] - _newValue;
// Check if the change is within limitations
if (
_newValue > s_planBoostUpperLimit ||
_newValue < s_planBoostLowerLimit ||
valDif > s_planBoostStepLimit
) revert Parameters_ChangeNotWithinBounds();
// Save the timestamp of the current change as latest
s_lastPlanBoostChange[_plan] = block.timestamp;
// Add the current change to the pending changes buffer
PlanBoostChange memory newChange = PlanBoostChange({
plan: _plan,
newValue: _newValue,
initiated: block.timestamp
});
s_planBoostChanges[s_pbcIdxEnd] = newChange;
s_pbcIdxEnd = (s_pbcIdxEnd + 1) % 4; // As we are using a circular buffer, the indexing of the array "wraps around"
// back to the beginning of the array after adding to the end of the buffer
emit ParameterChangeRequested(
"PlanBoost",
_plan,
s_planBoost[_plan],
_newValue
);
}
function changePenaltyFee(
uint256 _plan,
uint256 _newValue
) external onlyCentralPlatform {
// Check if plan is valid
if (_plan >= 4) {
revert Parameters_PlanOrTierNotValid();
}
// We check if the cooldown period for the change has expired
if (
block.timestamp - s_lastPenaltyChange[_plan] <
30 days * s_parameterChangeCooldown
) revert Parameters_CooldownNotOver();
_checkPendingPenaltyChanges();
// Find the absolute value of the change difference
uint256 valDif = _newValue > s_penaltyFee[_plan]
? _newValue - s_penaltyFee[_plan]
: s_penaltyFee[_plan] - _newValue;
// Check if the change is within limitations
if (
_newValue > s_penaltyFeeUpperLimit[_plan] ||
_newValue < s_penaltyFeeLowerLimit ||
valDif > s_penaltyFeeStepLimit
) revert Parameters_ChangeNotWithinBounds();
// Save the timestamp of the current change as latest
s_lastPenaltyChange[_plan] = block.timestamp;
// Add the current change to the pending changes buffer
PenaltyChange memory newChange = PenaltyChange({
plan: _plan,
newValue: _newValue,
initiated: block.timestamp
});
s_penaltyChanges[s_pcIdxEnd] = newChange;
s_pcIdxEnd = (s_pcIdxEnd + 1) % 4; // As we are using a circular buffer, the indexing of the array "wraps around"
// back to the beginning of the array after adding to the end of the buffers
emit ParameterChangeRequested(
"PenaltyFee",
_plan,
s_penaltyFee[_plan],
_newValue
);
}
///////////////////////////////////
// Internal Functions //
///////////////////////////////////
// Function for checking if any pending boost changes have passed their grace period
function _checkPendingBoostChanges() internal {
// First check for tier boost changes
for (uint256 i = s_tbcIdxStart; i != s_tbcIdxEnd; i = (i + 1) % 6) {
// For loop goes around the circular buffer
if (
block.timestamp - s_tierBoostChanges[i].initiated >
s_parameterChangeGrace * 30 days
) {
s_tierBoost[s_tierBoostChanges[i].tier] = s_tierBoostChanges[i]
.newValue;
s_tbcIdxStart = (s_tbcIdxStart + 1) % 6; // When a pending change has expired, it is deleted by simply moving
// the start index (which, again, wraps around) past it
} else {
break; // The order of addintion to the buffer is temporal, so if we come across a pending change
// not past the grace period, we know the ones after it are also not past it
}
}
// Then check for plan boost changes
for (uint256 i = s_pbcIdxStart; i != s_pbcIdxEnd; i = (i + 1) % 4) {
// For loop goes around the circular buffer
if (
block.timestamp - s_planBoostChanges[i].initiated >
s_parameterChangeGrace * 30 days
) {
s_planBoost[s_planBoostChanges[i].plan] = s_planBoostChanges[i]
.newValue;
s_pbcIdxStart = (s_pbcIdxStart + 1) % 4; // When a pending change has expired, it is deleted by simply moving
// the start index (which, again, wraps around) past it
} else {
break; // The order of addintion to the buffer is temporal, so if we come across a pending change
} // not past the grace period, we know the ones after it are also not past it
}
}
// Function for checking if any pending penalty fee changes passed their grace period
function _checkPendingPenaltyChanges() internal {
for (uint256 i = s_pcIdxStart; i != s_pcIdxEnd; i = (i + 1) % 4) {
// For loop goes around the circular buffer
if (
block.timestamp - s_penaltyChanges[i].initiated >
s_parameterChangeGrace * 30 days
) {
s_penaltyFee[s_penaltyChanges[i].plan] = s_penaltyChanges[i]
.newValue;
s_pcIdxStart = (s_pcIdxStart + 1) % 4; // When a pending change has expired, it is deleted by simply moving
// the start index (which, again, wraps around) past it
} else {
break; // The order of addintion to the buffer is temporal, so if we come across a pending change
} // not past the grace period, we know the ones after it are also not past it
}
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
/**
* @notice ERC20 token that implements EIP-2612 permit functionality.
* @notice Total supply of tokens is minted upon deployment and none of the tokens can be minted ever again.
* @notice Token owners can burn their tokens.
*/
contract ZNDToken is ERC20, Ownable2Step, ERC20Permit, ERC20Burnable {
error RenouncingOwnershipIsDisabled();
/**
* @dev Initial and final supply of tokens. Tokens can not be minted after this contract is deployed.
*/
uint256 constant public TOTAL_SUPPLY = 700_000_000;
constructor() ERC20("ZNDToken", "ZND") ERC20Permit("ZNDToken") Ownable(msg.sender) {
_mint(msg.sender, TOTAL_SUPPLY * 10 ** decimals());
}
/**
* @notice Always reverts in order to prevent losing ownership.
*/
function renounceOwnership() public view override onlyOwner {
revert RenouncingOwnershipIsDisabled();
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;
import {ZNDParameters} from "./ZNDParameters.sol";
import {IZNDTreasury} from "./IZNDTreasury.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/**
* @notice Implements treasury related functionality.
* @notice Enables funding of rewards and discretionary pools.
* @notice Enables withdrawing from discretionary and fees pools.
* @notice Implements vesting schedule and controls payouts to token recipients.
*/
contract ZNDTreasury is ZNDParameters, IZNDTreasury {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
///////////////////////////////////
// Treasury related accounts //
///////////////////////////////////
// Discretionary payments withdrawal account
address public s_withdrawalAccount;
// Discretionary over-limit withdrawal account
address public s_specialAccount;
///////////////////////////////////
// Treasury pool balances //
///////////////////////////////////
// Amount of ZND tokens in the rewards pool
uint256 public s_rewardsPoolBalance;
// Amount of ZND tokens in the fees pool
uint256 public s_feesPoolBalance;
// Amount of ZND tokens in the discretionary pool
uint256 public s_discretionaryPoolBalance;
// Struct which keeps track of vesting policy
mapping(address => VestingData) private s_recipients;
///////////////////////////////////
// Treasury parameters //
///////////////////////////////////
/// @dev withdrawal limit for discretionary and fees pools
uint256 public s_withdrawalLimit;
///////////////////////////////
// Modifiers //
///////////////////////////////
modifier onlyTreasuryAccount() {
if (
msg.sender != s_withdrawalAccount &&
msg.sender != s_specialAccount &&
msg.sender != owner()
) revert Treasury__NotTreasuryAccount();
_;
}
///////////////////////////////
// Constructor //
///////////////////////////////
/// @notice this will deploy the vesting table.
constructor(
address _zndPlatform,
address _withdrawalAccount,
address _specialAccount,
uint256 _initialDiscretionaryPoolBalance,
uint256 _initialRewardsPoolBalance,
VestingPayload[] memory _vestingPayload
) ZNDParameters(_zndPlatform) {
if (_withdrawalAccount == address(0)) revert Treasury__InvalidAddress();
if (_specialAccount == address(0)) revert Treasury__InvalidAddress();
if (_withdrawalAccount == owner())
revert Treasury__WithdrawalAccountCanNotBeOwner();
if (_withdrawalAccount == _specialAccount)
revert Treasury__WithdrawalAccountCanNotBeSpecialAccount();
if (_specialAccount == owner())
revert Treasury__SpecialAccountCanNotBeOwner();
s_withdrawalAccount = _withdrawalAccount;
s_specialAccount = _specialAccount;
s_discretionaryPoolBalance = _initialDiscretionaryPoolBalance;
s_rewardsPoolBalance = _initialRewardsPoolBalance;
// Vesting Policy Deployment
for (uint256 i = 0; i < _vestingPayload.length; i++) {
// get the vesting recipient
address recipient = _vestingPayload[i].recipient;
// cannot be special account
if (recipient == _specialAccount)
revert Treasury__SpecialAccountCanNotBeVestingRecipient();
// also cannot be owner
if (recipient == owner())
revert Treasury__OwnerAccountCanNotBeVestingRecipient();
// making sure the address is not duplicate,
// if there was no initial unlock, and therefore `availableTokens`
// are 0, there will be some non-0 number of unlocks left
// on the other hand, if all the funds are meant to be available
// immmediately, `availableTokens` will be non-0
if (
s_recipients[recipient].availableTokens > 0 ||
s_recipients[recipient].numberOfUnlocksLeft > 0
) revert Treasury__DuplicateVestingAddress();
uint256 amountToReceivePerUnlock = 0;
if (_vestingPayload[i].numberOfDays > 0) {
// making sure that quantity is divisible by number of days
if (
_vestingPayload[i].quantityToUnlockOverTime %
_vestingPayload[i].numberOfDays >
0
)
revert Treasury__QuantityUnlockOverTimeNotDivisibleByNumberOfDays();
amountToReceivePerUnlock =
_vestingPayload[i].quantityToUnlockOverTime /
_vestingPayload[i].numberOfDays;
}
s_recipients[recipient] = VestingData({
availableTokens: _vestingPayload[i].initialUnlock,
numberOfUnlocksLeft: _vestingPayload[i].numberOfDays,
amountToReceivePerUnlock: amountToReceivePerUnlock,
nextUnlockTimestamp: block.timestamp +
_vestingPayload[i].cliffPeriodEnd
});
}
}
///////////////////////////////////
// External Functions //
///////////////////////////////////
/// @inheritdoc IZNDTreasury
function fundRewardsPool(uint256 _amount) external {
s_rewardsPoolBalance += _amount;
emit Treasury__Funding(msg.sender, _amount, TreasuryPool.RewardsPool);
IERC20(s_zndToken).safeTransferFrom(msg.sender, address(this), _amount);
}
/// @inheritdoc IZNDTreasury
function fundDiscretionaryPool(uint256 _amount) external {
s_discretionaryPoolBalance += _amount;
emit Treasury__Funding(
msg.sender,
_amount,
TreasuryPool.DiscretionaryPool
);
IERC20(s_zndToken).safeTransferFrom(msg.sender, address(this), _amount);
}
/// @inheritdoc IZNDTreasury
function withdrawFromDiscretionaryPool(
address _to,
uint256 _amount
) external onlyTreasuryAccount {
_checkWithdrawalValidity(_amount);
// amount is less than or equal limit, or the sender is owner or special account, execute payment
// check available funds
if (_amount > s_discretionaryPoolBalance)
revert Treasury__InsufficientFunds();
s_discretionaryPoolBalance -= _amount;
emit Treasury__Withdrawal(
msg.sender,
_to,
_amount,
TreasuryPool.DiscretionaryPool
);
IERC20(s_zndToken).safeTransfer(_to, _amount);
}
/// @inheritdoc IZNDTreasury
function withdrawFromFeesPool(
address _to,
uint256 _amount
) external onlyTreasuryAccount {
_checkWithdrawalValidity(_amount);
// amount is less than or equal limit, or the sender is owner or special account, execute payment
// check available funds
if (_amount > s_feesPoolBalance) revert Treasury__InsufficientFunds();
s_feesPoolBalance -= _amount;
emit Treasury__Withdrawal(
msg.sender,
_to,
_amount,
TreasuryPool.FeesPool
);
IERC20(s_zndToken).safeTransfer(_to, _amount);
}
/// @inheritdoc IZNDTreasury
function setWithdrawLimit(uint256 _limit) external onlyOwner {
s_withdrawalLimit = _limit;
emit Treasury__WithdrawalLimitUpdated(msg.sender, _limit);
}
/// @inheritdoc IZNDTreasury
function setWithdrawalAccount(address _account) external onlyOwner {
if (_account == address(0)) revert Treasury__InvalidAddress();
if (_account == owner())
revert Treasury__WithdrawalAccountCanNotBeOwner();
if (_account == s_specialAccount)
revert Treasury__WithdrawalAccountCanNotBeSpecialAccount();
s_withdrawalAccount = _account;
emit Treasury__WithdrawalAccountUpdated(msg.sender, _account);
}
/// @inheritdoc IZNDTreasury
function setSpecialAccount(address _account) external onlyOwner {
if (_account == address(0)) revert Treasury__InvalidAddress();
if (_account == owner()) revert Treasury__SpecialAccountCanNotBeOwner();
if (_account == s_withdrawalAccount)
revert Treasury__WithdrawalAccountCanNotBeSpecialAccount();
s_specialAccount = _account;
emit Treasury__SpecialAccountUpdated(msg.sender, _account);
}
/**
* @notice Changes the owner of the contract.
* @notice owner can not withdraw from vesting pool.
* @notice owner can update parameters of the treasury.
* @param _account address of the new owner.
* @dev requires caller to be owner, reverts with OwnableUnauthorizedAccount error otherwise.
* @dev requires account to be non zero address, reverts with Treasury__InvalidAddress error otherwise.
* @dev requires account not to be special account, reverts with Treasury__SpecialAccountCanNotBeOwner error otherwise.
* @dev requires account not to be regular account, reverts with Treasury__WithdrawalAccountCanNotBeOwner error otherwise.
* @dev emits Treasury__OwnerUpdated event.
*/
function transferOwnership(address _account) public override onlyOwner {
if (_account == address(0)) revert Treasury__InvalidAddress();
if (_account == s_specialAccount)
revert Treasury__SpecialAccountCanNotBeOwner();
if (_account == s_withdrawalAccount)
revert Treasury__WithdrawalAccountCanNotBeOwner();
super.transferOwnership(_account);
}
/**
* @notice Always reverts in order to prevent losing ownership.
*/
function renounceOwnership() public view override onlyOwner {
revert Treasury__RenouncingOwnershipIsDisabled();
}
/// @inheritdoc IZNDTreasury
function requestPayout(uint256 _amount) external returns (bool) {
// Update recipients available tokens based on vesting policy for recipient
VestingData memory recipientVestingPolicy = s_recipients[msg.sender];
uint256 unlockCount = 0;
uint256 lastUnlockTimestamp = recipientVestingPolicy
.nextUnlockTimestamp;
// if enough time has passed get the number of unlocks
if (block.timestamp > lastUnlockTimestamp) {
// since division floors, `+1` is necessary to get the corret unlockCount
// e.g.: if one more hour than it's necessary for the next unlock
// to occur has passed, that would give the `block.timestamp - lastUnlockTimestamp`
// of 3600, and divided by 1 days, would be 0, even though enough time has passed
unlockCount = (block.timestamp - lastUnlockTimestamp) / 1 days + 1;
}
// if there are unlocks, update the vesting data
if (unlockCount > 0) {
unlockCount = _min(
unlockCount,
recipientVestingPolicy.numberOfUnlocksLeft
);
// update the last unlocked timestamp
s_recipients[msg.sender].nextUnlockTimestamp += (1 days *
unlockCount);
// update the number of unlocks left
s_recipients[msg.sender].numberOfUnlocksLeft -= unlockCount;
// update the available tokens
s_recipients[msg.sender].availableTokens +=
unlockCount *
recipientVestingPolicy.amountToReceivePerUnlock;
}
// Transfer the requested amount to the recipient if the sufficient funds are available
// if there are insufficient funds, stop the transaction with success flag set to false
if (s_recipients[msg.sender].availableTokens < _amount) return false;
s_recipients[msg.sender].availableTokens -= _amount;
emit Treasury__VestingPayoutCompleted(msg.sender, _amount);
// transfer the tokens to the recipient
IERC20(s_zndToken).safeTransfer(msg.sender, _amount);
// flag payout as a successful one
return true;
}
/// @inheritdoc IZNDTreasury
function getClaimableVestingAmount(
address _address
) external view returns (uint256) {
VestingData memory recipientVestingPolicy = s_recipients[_address];
uint256 claimableTokenAmount = recipientVestingPolicy.availableTokens;
uint256 unlockCount = 0;
uint256 lastUnlockTimestamp = recipientVestingPolicy
.nextUnlockTimestamp;
// if enough time has passed get the number of unlocks
if (block.timestamp > lastUnlockTimestamp) {
// since division floors, `+1` is necessary to get the corret unlockCount
// e.g.: if one more hour than it's necessary for the next unlock
// to occur has passed, that would give the `block.timestamp - lastUnlockTimestamp`
// of 3600, and divided by 1 days, would be 0, even though enough time has passed
unlockCount = (block.timestamp - lastUnlockTimestamp) / 1 days + 1;
}
// if there are unlocks, add them to the claimable amount
if (unlockCount > 0) {
unlockCount = _min(
unlockCount,
recipientVestingPolicy.numberOfUnlocksLeft
);
claimableTokenAmount +=
unlockCount *
recipientVestingPolicy.amountToReceivePerUnlock;
}
return claimableTokenAmount;
}
///////////////////////////////////////////
// Internal Functions //
///////////////////////////////////////////
function _checkWithdrawalValidity(uint256 _amount) internal view {
// amount must be greater than 0
if (_amount == 0) revert Treasury__AmountNotSpecified();
// if amount is over withdrawal limit
if (_amount > s_withdrawalLimit) {
// check if caller is neither owner nor special account
if (msg.sender != owner() && msg.sender != s_specialAccount) {
// revert
revert Treasury__OverLimitWithdrawalNotAllowed();
}
}
}
function _min(
uint256 _first,
uint256 _second
) internal pure returns (uint256) {
return _first < _second ? _first : _second;
}
}{
"optimizer": {
"enabled": true,
"runs": 10000
},
"viaIR": true,
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_zndPlatform","type":"address"},{"internalType":"address","name":"_discretionaryWithdrawalAccount","type":"address"},{"internalType":"address","name":"_overLimitWithdrawalAccount","type":"address"},{"internalType":"uint256","name":"_initialDailyReward","type":"uint256"},{"internalType":"uint256","name":"_initialDiscretionaryPoolBalance","type":"uint256"},{"internalType":"uint256","name":"_initialRewardsPoolBalance","type":"uint256"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"quantityToUnlockOverTime","type":"uint256"},{"internalType":"uint256","name":"numberOfDays","type":"uint256"},{"internalType":"uint256","name":"cliffPeriodEnd","type":"uint256"},{"internalType":"uint256","name":"initialUnlock","type":"uint256"}],"internalType":"struct IZNDTreasury.VestingPayload[]","name":"_vestingPayload","type":"tuple[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"Parameters_ChangeNotWithinBounds","type":"error"},{"inputs":[],"name":"Parameters_CooldownNotOver","type":"error"},{"inputs":[],"name":"Parameters_PlanOrTierNotValid","type":"error"},{"inputs":[],"name":"Parameters_PoolsAreNotEmpty","type":"error"},{"inputs":[],"name":"Reward_ComputationAlreadyStarted","type":"error"},{"inputs":[],"name":"Reward_InsufficientFundsInPool","type":"error"},{"inputs":[],"name":"Reward_NotAStakeholder","type":"error"},{"inputs":[],"name":"Reward_WithdrawInsufficientFunds","type":"error"},{"inputs":[],"name":"Reward_ZeroAmountWithdrawalNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"Stake_AddressNotFound","type":"error"},{"inputs":[],"name":"Stake_ComprehensiveRewardDistributionOngoing","type":"error"},{"inputs":[],"name":"Stake_DoesNotExist","type":"error"},{"inputs":[],"name":"Stake_NoStakesProvided","type":"error"},{"inputs":[],"name":"Stake_NoWithdrawalsProvided","type":"error"},{"inputs":[],"name":"Stake_NotOwner","type":"error"},{"inputs":[],"name":"Stake_WithdrawInsufficientFunds","type":"error"},{"inputs":[],"name":"Stake_ZeroAmountStakeNotAllowed","type":"error"},{"inputs":[],"name":"Stake_ZeroAmountWithdrawalNotAllowed","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[],"name":"Treasury__AmountNotSpecified","type":"error"},{"inputs":[],"name":"Treasury__DuplicateVestingAddress","type":"error"},{"inputs":[],"name":"Treasury__InsufficientFunds","type":"error"},{"inputs":[],"name":"Treasury__InvalidAddress","type":"error"},{"inputs":[],"name":"Treasury__NotTreasuryAccount","type":"error"},{"inputs":[],"name":"Treasury__OverLimitWithdrawalNotAllowed","type":"error"},{"inputs":[],"name":"Treasury__OwnerAccountCanNotBeVestingRecipient","type":"error"},{"inputs":[],"name":"Treasury__QuantityUnlockOverTimeNotDivisibleByNumberOfDays","type":"error"},{"inputs":[],"name":"Treasury__RenouncingOwnershipIsDisabled","type":"error"},{"inputs":[],"name":"Treasury__SpecialAccountCanNotBeOwner","type":"error"},{"inputs":[],"name":"Treasury__SpecialAccountCanNotBeVestingRecipient","type":"error"},{"inputs":[],"name":"Treasury__WithdrawalAccountCanNotBeOwner","type":"error"},{"inputs":[],"name":"Treasury__WithdrawalAccountCanNotBeSpecialAccount","type":"error"},{"inputs":[],"name":"Znd_InvalidSigner","type":"error"},{"inputs":[],"name":"Znd_NotEOA","type":"error"},{"inputs":[],"name":"Znd_NotZNDPlatform","type":"error"},{"inputs":[],"name":"Znd_ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newSize","type":"uint256"}],"name":"BatchSizeChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"ComputedDailyRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DailyRewardChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"DailyRewardOffsetChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"}],"name":"DidNotDistributeAllRewardsToSelf","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"limit","type":"uint256"}],"name":"DiscretionaryPoolWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"}],"name":"DistributedDailyReward","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"FinishedComprehensiveRewardDistribution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"changeType","type":"string"},{"indexed":false,"internalType":"uint256","name":"planOrTier","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ParameterChangeRequested","type":"event"},{"anonymous":false,"inputs":[],"name":"RewardComputationStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"stakeIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"numberOfStakes","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"StakedCentral","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"stakeId","type":"uint256"},{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"enum IZNDBase.Plan","name":"plan","type":"uint8"}],"name":"StakedEOA","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"funder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"enum IZNDTreasury.TreasuryPool","name":"pool","type":"uint8"}],"name":"Treasury__Funding","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"updater","type":"address"},{"indexed":true,"internalType":"address","name":"newAccount","type":"address"}],"name":"Treasury__SpecialAccountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Treasury__VestingPayoutCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"withdrawAccount","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawTo","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"enum IZNDTreasury.TreasuryPool","name":"pool","type":"uint8"}],"name":"Treasury__Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"updater","type":"address"},{"indexed":true,"internalType":"address","name":"newAccount","type":"address"}],"name":"Treasury__WithdrawalAccountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"updater","type":"address"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"Treasury__WithdrawalLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawRewardsCentral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWithdrawn","type":"uint256"}],"name":"WithdrawRewardsEOA","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeId","type":"uint256"},{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWithdrawn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penatlyFee","type":"uint256"}],"name":"WithdrawStakeEOA","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"stakeIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"numberOfWithdrawals","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalPenalites","type":"uint256"}],"name":"WithdrawStakesCentral","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areAllRewardsDistributed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_newValue","type":"uint256"}],"name":"changePenaltyFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_newValue","type":"uint256"}],"name":"changePlanBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tier","type":"uint256"},{"internalType":"uint256","name":"_newValue","type":"uint256"}],"name":"changeTierBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"distributeRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_tier","type":"uint256"},{"internalType":"uint256","name":"_noLogs","type":"uint256"}],"name":"distributeRewardsSpecific","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"fundDiscretionaryPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"fundRewardsPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum IZNDBase.Tier","name":"_tier","type":"uint8"},{"internalType":"enum IZNDBase.Plan","name":"_plan","type":"uint8"}],"name":"getAmountStakedInPool","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"getClaimableVestingAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"getRewardAmountOfAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeId","type":"uint256"}],"name":"getStake","outputs":[{"components":[{"internalType":"uint256","name":"stakeId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"stakedAt","type":"uint256"},{"internalType":"address","name":"stakeholder","type":"address"},{"components":[{"internalType":"enum IZNDBase.Tier","name":"tier","type":"uint8"},{"internalType":"enum IZNDBase.Plan","name":"plan","type":"uint8"}],"internalType":"struct IZNDStaking.Pool","name":"pool","type":"tuple"}],"internalType":"struct IZNDStaking.Stake","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"requestPayout","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"s_dailyReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_discretionaryPoolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_feesPoolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_nextStakeId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_parameterChangeCooldown","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_parameterChangeGrace","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_penaltyFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_penaltyFeeLowerLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_penaltyFeeStepLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_penaltyFeeUpperLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_planBoost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_planBoostLowerLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_planBoostStepLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_planBoostUpperLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_planDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_rewardsPoolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_specialAccount","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_tierBoost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_tierBoostLowerLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_tierBoostStepLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_tierBoostUpperLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_withdrawalAccount","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_withdrawalLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_zndPlatform","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_zndToken","outputs":[{"internalType":"contract ZNDToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"setDailyReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newOffset","type":"uint256"}],"name":"setResetPointOffset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"setSpecialAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"setWithdrawLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"setWithdrawalAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"tier","type":"uint256"},{"internalType":"uint256","name":"plan","type":"uint256"}],"internalType":"struct IZNDStaking.StakePayload[]","name":"_stakes","type":"tuple[]"}],"name":"stakeCentral","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"stakeEOA","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startRewardComputation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawFromDiscretionaryPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawFromFeesPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawRewardsCentral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"withdrawRewardsEOA","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"withdrawStakeEOA","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"stakeId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct IZNDStaking.WithdrawCentralPayload[]","name":"_withdrawals","type":"tuple[]"}],"name":"withdrawStakesCentral","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
6103408060405234610d8b576177b2803803809161001d8285610d90565b833981019060e081830312610d8b5761003581610db3565b9161004260208301610db3565b61004e60408401610db3565b9360608401519160808501519060a08601519560c08101519060018060401b038211610d8b570185601f82011215610d8b578051906001600160401b0382116108a257604051966100a560208460051b0189610d90565b828852602060a0818a01940283010191818311610d8b57602001925b828410610d215750505050604051956100db604088610d90565b600a87526020870190695a4e445374616b696e6760b01b825260405193610103604086610d90565b600185526020850193603160f81b85523315610d0b57600180546001600160a01b031990811690915560008054339281168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001600160a01b03881615610cfa5760a0889052611c4d8181016001600160401b038111838210176108a2578291615b65833903906000f08015610cee5760805260647f3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff81905560c87fa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c81905560dc7fc3a24b0501bd2c13a7e57f2db4369ec4c223447539fc0724a9d55ac4a06ebd4d5560f07fcbc4e5fb02c3d1de23a9f1e014b4d2ee5aeaea9505df5e855c9210bf472495af556101047f83ec6a1f0257b830b5e016457c9cf1435391bf56cc98f369a58a54fe937724655561012c7f405aad32e1adbac89bb7f176e338b8fc6e994ca210c9bb7bdca249b4659422508190557f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec83905560967fabd6e7cb50984ff9c2f3e18a2660c3353dadf4e3291deeb275dae2cd1e44fe05557f91da3fd0782e51c6b3986e9e672fd566868e71f3dbc2d6c2cd6fbb3e361af2a7919091557f2e174c10e159ea99b867ce3205125c24a42d128804e4070ed6fcc8cc98166aa0819055601e7fac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b55605a7fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e05560b47f679795a0195a1b76cdebb7c51d74e058aee92919b8c3389af86ef24535e8a28c556101687f88601476d11616a71c5be67555bd1dff4b1cbf21533d2669b768b61518cfe1c3557f05b8ccbb9d4d8fb16ea74ce3c29a41f1b461fbdaff4714a0d9a8eb05499746bc8290557f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b556102587f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a556104b07fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc05560c08190526103e860e081905260016101008190526101209290925261014081905261016082905260006101808190526101a08390526101f47f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8557f3e5fec24aa4dc4e5aee2e025e51e1392c72a2500577559fae9665c6d52bd6a31919091556105dc7f8819ef417987f8ae7a81f42cdfb18815282fe989326fbff903d13cf0e03ace29556109c47f75f96ab15d697e93042dc45b5c896c4b27e89bb6eaf39475c5c371cb2513f7d2556101c09190915260036101e0819052427fa0a618d80eda9243166be83cb7421d97e9dab6ddddd3c70ac7a6b4440256e8e78190557f8e0cc0f1f0504b4cb44a23b328568106915b169e79003737a7b094503cdbeeb08190557f67dcc86da9aaaf40a183002157e56801115aa6057705e43279b4c1c90942d6b28190557f81d0999fde243adcc41b7fa1be5cea14f789e3a6065b815ac58f4bc0838c31558190557f533efb5c9f032d0e72b35f5d59b231dc7a9fb94625f73b3c45c394126326354c8190557f50039cf134a124858bd88bbc9225ec3c537b89a0e9237ce39fe1813e6edf82578190557fe14cf4d84b2ff434db2c3d715ad03acb36d95ed6f766d46660154cee72012d718190557f33a28b70ecab075fc507d0cb5ffac06e3bc912aa6c671a1fa4ccb48318e19b118190557f44eb26011dd1cf82e0b45a2fb60b23f01ab68cabbe2fe1f3e7b70c0873d5dc5b8190557f76ace02705df1df747e114090468e0edd81a8d99edc641fff18613cd34df0a6e81905560396020527f619fcd35f40c77da8aa64d5f98c1fed669135ecc4f245ce6667721b157f7098f8190557f6f07f90659cdc781ea4d9fa69455ad8c2ec7b10a59bb4222a9a50afe92849fb08190557f14da11d3a545d46fb88e4cc5018cec76493b6788d69758d44d9e98f3fad639298190559082527f7a1d31c67145dccaf0cb9ca8d00d91c495aa0503124bb9c46a2ce884ec3cf3d755603181905560328190556033819055603481905560358190556036556001600160a01b0316998a15610cdd576001600160a01b0316998a15610cdd576000546001600160a01b0316818114610ccc578b8214610cbb578b14610caa57603a80546001600160a01b0319908116929092179055603b80549091168b179055603e55603c5560005b86518110156109a8576001600160a01b036107b98289610dc7565b51511690898214610997576000546001600160a01b031682146109865781600052603f6020526040600020541580159061096c575b61095b5760006040610800838b610dc7565b5101516108ce575b6080610814838b610dc7565b510151926040610824848c610dc7565b510151916060610834858d610dc7565b5101514201908142116108b8576040519360808501936001600160401b038511868610176108a2576001976003956040528652602086019182526040860192835260608601938452600052603f6020526040600020945185555186850155516002840155519101550161079e565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60206108da838b610dc7565b51015160406108e9848c610dc7565b5101519081156109475706610938576020610904838b610dc7565b5101516040610913848c610dc7565b510151918215610924575004610808565b634e487b7160e01b81526012600452602490fd5b631d6396f160e31b8152600490fd5b634e487b7160e01b83526012600452602483fd5b6322708f3760e21b60005260046000fd5b5081600052603f60205260016040600020015415156107ee565b632f2873bb60e11b60005260046000fd5b6378852d5560e01b60005260046000fd5b50866109b381610df1565b6102a0526109c084610fb5565b6102c052519020918261026052519020806102805246610220526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152610a2b60c082610d90565b5190206102005230610240527f82186402b6a35725297ac7691ca0a6e5be47eef3d200a5bc491a4e89d99cadde6102e0527fbda4078728c0ae5140c1cd85bf1abff4d86904b62dc8d2823ae88a05b34e2e37610300527f66a29e1886f085dbd8ec4fc1f6b13da644ad0efad4477114339037860a802dfe6103205260a052600160605560006061556080556000607f556001606255604051614a11908161115482396080518181816104bf01528181610f760152818161118101528181611a0601528181611e180152818161265f01528181612b5e01528181612d5d0152818161315d01528181613306015281816136a20152613841015260a0518181816105a50152818161081501528181610b4f01528181610e1001528181611554015281816115ea015281816116d00152818161171e01528181611abe01528181611c7401528181611d8101528181611e9901528181611f550152611fe2015260c051818181610d57015261145f015260e051818181610c090152611f12015261010051818181610d2f0152611a42015261012051818181610b13015261192c0152610140518181816117dd0152611a7d01526101605181818161132901526119040152610180518181816106730152610a6201526101a051818181610a3a0152610db401526101c05181818161108501528181613f9b015261417101526101e05181818161085e01528181610b98015281816114f60152611767015261020051816143520152610220518161440f01526102405181614323015261026051816143a1015261028051816143c701526102a0518161123701526102c0518161126301526102e0518161281c01526103005181613083015261032051816132150152f35b639249f17560e01b60005260046000fd5b631a0deb8760e31b60005260046000fd5b63939cd46160e01b60005260046000fd5b63526e939f60e11b60005260046000fd5b6040513d6000823e3d90fd5b633eb9379d60e01b60005260046000fd5b631e4fbdf760e01b600052600060045260246000fd5b60a084830312610d8b576040519060a08201906001600160401b038211838310176108a25760a092602092604052610d5887610db3565b815282870151838201526040870151604082015260608701516060820152608087015160808201528152019301926100c1565b600080fd5b601f909101601f19168101906001600160401b038211908210176108a257604052565b51906001600160a01b0382168203610d8b57565b8051821015610ddb5760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b90815160208110600014610e89575090601f815111610e2d576020815191015160208210610e1d571790565b6000198260200360031b1b161790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b838110610e715750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001610e4f565b6001600160401b0381116108a257604154600181811c91168015610fab575b6020821014610f9557601f8111610f5f575b50602092601f8211600114610efa5792819293600092610eef575b50508160011b916000199060031b1c19161760415560ff90565b015190503880610ed5565b601f198216936041600052806000209160005b868110610f475750836001959610610f2e575b505050811b0160415560ff90565b015160001960f88460031b161c19169055388080610f20565b91926020600181928685015181550194019201610f0d565b6041600052601f6020600020910160051c810190601f830160051c015b818110610f895750610eba565b60008155600101610f7c565b634e487b7160e01b600052602260045260246000fd5b90607f1690610ea8565b9081516020811060001461103d575090601f815111610fe1576020815191015160208210610e1d571790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b8381106110255750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001611003565b6001600160401b0381116108a257604254600181811c91168015611149575b6020821014610f9557601f8111611113575b50602092601f82116001146110ae57928192936000926110a3575b50508160011b916000199060031b1c19161760425560ff90565b015190503880611089565b601f198216936042600052806000209160005b8681106110fb57508360019596106110e2575b505050811b0160425560ff90565b015160001960f88460031b161c191690553880806110d4565b919260206001819286850151815501940192016110c1565b6042600052601f6020600020910160051c810190601f830160051c015b81811061113d575061106e565b60008155600101611130565b90607f169061105c56fe6080604052600436101561001257600080fd5b60003560e01c806309a69f57146120415780630aa4353314611fc457806314596a8214611f9757806315e3a6dc14611f7957806320dd50df14611f3557806321ea1bc414611efa57806322c45b6714611ece5780632420865714611e3d5780632c4eb8b214611db35780632e4edf1714611d65578063301a3e9514611cfe57806334238dd814611c59578063370f82fe14611c3b578063428d0b2514611b4657806342ba8c8914611aa057806342c9b7dd14611a6557806346b9c06914611a2a57806348efbf33146119e65780634abdf2e0146119985780634ec1b85814611971578063549013be14611705578063564ac5f514611674578063567b60be14611648578063656e84301461161c578063663ab7e1146115ce57806366ba3392146115b0578063677e0b271461151957806367936d13146114de5780636f4a2cd0146114c5578063715018a614611482578063725decc41461144757806379ba5097146113735780637ef6e8bd1461134c57806380e5deb01461131157806384b0196e1461121f5780638da5cb5b146111f85780638f97e3a0146111d05780639043ec6a146110da57806391eedf39146110a8578063990717441461106d5780639c82e39014610fc75780639f91c58d14610f9b578063abde825414610f11578063b03b65c414610e41578063b1b2f6eb14610df5578063b7fd4a6714610dd7578063b80adc5c14610d9c578063c04a6a4f14610b36578063ca26930014610afb578063cc37f730146107fc578063ce325bf8146106b4578063d179bd2214610696578063dad74b3f1461065b578063dc9139761461063d578063e30c397814610616578063e49f858a14610582578063ef772a6014610564578063f2b6c74914610418578063f2fde38b146102e15763fc65baaf146102b057600080fd5b346102dc5760206003193601126102dc5760043560005260026020526020604060002054604051908152f35b600080fd5b346102dc5760206003193601126102dc576001600160a01b036103026120b1565b61030a613f82565b1680156103ee576001600160a01b03603b541681146103c4576001600160a01b03603a5416811461039a5761033d613f82565b807fffffffffffffffffffffffff000000000000000000000000000000000000000060015416176001556001600160a01b03600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b7f939cd4610000000000000000000000000000000000000000000000000000000060005260046000fd5b7f9249f1750000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa4dd273e0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc576104316120b1565b6024356001600160a01b03603a54163314158061054f575b8061053a575b6105105761045c816140d8565b603e54918282116104e657610474826104e494612573565b603e55604051828152600160208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b005b7f6643a25f0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f2636942f0000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b036000541633141561044f565b506001600160a01b03603b5416331415610449565b346102dc5760006003193601126102dc576020606154604051908152f35b346102dc5760206003193601126102dc5761059b6120b1565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576001600160a01b0316600052607e6020526020604060002054604051908152f35b7f8b2731aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206001600160a01b0360015416604051908152f35b346102dc5760006003193601126102dc576020603c54604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc576020603e54604051908152f35b346102dc5760206003193601126102dc576040516106d181612222565b600081526000602082015260006040820152600060608201526080604051916106f98361223e565b60008352600060208401520152600435600052605f60205260406000206001600160a01b036003820154169081156107d2576001600160a01b036040519261074084612222565b82548452600183015492602085019384526107706004600283015492604088019384526060880194855201613882565b936080860194855260405195518652516020860152516040850152511660608301525190815160068110156107bc5760c092602091608084015201516107b5816121b7565b60a0820152f35b634e487b7160e01b600052602160045260246000fd5b7fd9e125150000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5761080a3661212c565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576004811015610ad15780600052603960205261085c60406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000000908162278d00029162278d008304036109e05710610aa75761089d61416c565b8060005260056020526040600020548211600014610a8a578060005260056020526108cd60406000205483612573565b8160005260066020526040600020548311908115610a60575b8115610a36575b50610a0c578060005260396020524260406000205560405161090e81612206565b8181528260208201524260408201526036549060048210156109f657600361094f920260250190604060029180518455602081015160018501550151910155565b603654906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086036558160005260056020526040600020546040519260808452600a60808501527f50656e616c74794665650000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b7f573a59ba0000000000000000000000000000000000000000000000000000000060005260046000fd5b90507f000000000000000000000000000000000000000000000000000000000000000010836108ed565b7f0000000000000000000000000000000000000000000000000000000000000000841091506108e6565b806000526005602052610aa282604060002054612573565b6108cd565b7fc2c1e97b0000000000000000000000000000000000000000000000000000000060005260046000fd5b7ffc87de8c0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc57610b443661212c565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576006811015610ad157806000526037602052610b9660406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000000908162278d00029162278d008304036109e05710610aa757610bd7613f96565b8060005260036020526040600020548211600014610d7f57806000526003602052610c0760406000205483612573565b7f00000000000000000000000000000000000000000000000000000000000000008311908115610d55575b8115610d2b575b50610a0c5780600052603760205242604060002055604051610c5a81612206565b8181528260208201524260408201526032549060068210156109f6576003610c9b920260070190604060029180518455602081015160018501550151910155565b60325490600182018092116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f092600660c093066032558160005260036020526040600020546040519260808452600960808501527f54696572426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f00000000000000000000000000000000000000000000000000000000000000001083610c39565b7f000000000000000000000000000000000000000000000000000000000000000084109150610c32565b806000526003602052610d9782604060002054612573565b610c07565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc576020606054604051908152f35b346102dc5760206003193601126102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576104e4600435613792565b346102dc5760206003193601126102dc576001600160a01b03610e626120b1565b610e6a613f82565b1680156103ee576001600160a01b0360005416811461039a576001600160a01b03603b54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603a541617603a55337f6d0f78b3a4ae2472e08ee885ed47bfb1ace996c12d6ff019537c99821244de8d600080a3005b7fd06f5c380000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576104e4600435610f3481603e546122b5565b603e55604051818152600160208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b346102dc5760206003193601126102dc5760043560005260046020526020604060002054604051908152f35b346102dc5760206003193601126102dc576001600160a01b03610fe86120b1565b610ff0613f82565b1680156103ee576001600160a01b036000541681146103c4576001600160a01b03603a54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603b541617603b55337f786bbb915c47f5cb032b19e70cd81f673f1ee5c1ac711db6681470264d521125600080a3005b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5733600052607e6020526020600160406000200154607d541115604051908152f35b346102dc5760406003193601126102dc576110f36120b1565b6024356001600160a01b03603a5416331415806111bb575b806111a6575b6105105761111e816140d8565b603d54918282116104e657611136826104e494612573565b603d55604051828152600260208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b506001600160a01b0360005416331415611111565b506001600160a01b03603b541633141561110b565b346102dc5760206003193601126102dc5760206111ee6004356135de565b6040519015158152f35b346102dc5760006003193601126102dc5760206001600160a01b0360005416604051908152f35b346102dc5760006003193601126102dc5760e061125b7f000000000000000000000000000000000000000000000000000000000000000061453e565b61130d6112877f00000000000000000000000000000000000000000000000000000000000000006146a2565b916112ec6020936112de6040519361129f878661225a565b6000855260003681376040519788977f0f00000000000000000000000000000000000000000000000000000000000000895288015260e0870190612142565b908582036040870152612142565b90466060850152306080850152600060a085015283820360c0850152612183565b0390f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5760206001600160a01b03603b5416604051908152f35b346102dc5760006003193601126102dc57600154336001600160a01b03821603611419577fffffffffffffffffffffffff000000000000000000000000000000000000000016600155600054337fffffffffffffffffffffffff00000000000000000000000000000000000000008216176000556001600160a01b033391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5761149b613f82565b7f23dbc96b0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576104e46133ee565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760406003193601126102dc5760243567ffffffffffffffff81116102dc5761154a9036906004016120c7565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314611586576104e4916004356131f5565b7f163156450000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576020608054604051908152f35b346102dc576115dc366120f5565b906001600160a01b039392937f0000000000000000000000000000000000000000000000000000000000000000163314611586576020936111ee93613058565b346102dc5760206003193601126102dc5760043560005260066020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043560005260056020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc5736602460608402830101116102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec5760209160246111ee9201612c92565b346102dc576117133661212c565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576004811015610ad15780600052603860205261176560406000205442612573565b7f00000000000000000000000000000000000000000000000000000000000000009062278d0082029180830462278d0014901517156109e05710610aa7576117ab613f96565b8060005260046020526040600020548211600014611954578060005260046020526117db60406000205483612573565b7f0000000000000000000000000000000000000000000000000000000000000000831190811561192a575b8115611900575b50610a0c578060005260386020524260406000205560405161182e81612206565b8181528260208201524260408201526034549060048210156109f657600361186f920260190190604060029180518455602081015160018501550151910155565b603454906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086034558160005260046020526040600020546040519260808452600960808501527f506c616e426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f0000000000000000000000000000000000000000000000000000000000000000108361180d565b7f000000000000000000000000000000000000000000000000000000000000000084109150611806565b80600052600460205261196c82604060002054612573565b6117db565b346102dc5760006003193601126102dc5760206001600160a01b03603a5416604051908152f35b346102dc5760206003193601126102dc576004356119b4613f82565b806040556040519081527f075e0c6585fd90d164d2f7f5d5399e97900ed892807821f47be4d64c9eb5238160203392a2005b346102dc5760006003193601126102dc5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760206003193601126102dc576004356001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec57606154611b1c576020817fe056d7ccce0301fe61ac060fe377a3c89b179cd09fa62ef48618d5791a78d64892607f55604051908152a1005b7fac5cadc30000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576001600160a01b03611b676120b1565b16600052603f6020526060604060002060405190611b84826121d4565b80549081835260018101549260208101938452600360028301549260408301938452015494859101528192600094804211611c04575b509360209481611bcf575b8585604051908152f35b51611bf39450611bed92919080821015611bfc5750905b51906121c1565b906122b5565b82808080611bc5565b905090611be6565b611c12620151809142612573565b0460018101809111611c275794506020611bba565b602486634e487b7160e01b81526011600452fd5b346102dc5760006003193601126102dc576020603d54604051908152f35b346102dc5760006003193601126102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec57606254600214611cd4576002606255426063557faeb375a1784645db3075ba87abdf2952280755ad1967e93aea416f217386a1bb600080a1005b7ff62324030000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc5760043560068110156102dc576024359060048210156102dc57611d31826121b7565b6006820291808304600614901517156109e057611d4d916122b5565b60188110156109f65760209060640154604051908152f35b346102dc57611d73366120f5565b906001600160a01b039392937f0000000000000000000000000000000000000000000000000000000000000000163314611586576020936111ee936127ed565b346102dc5760206003193601126102dc576104e4600435611dd681603c546122b5565b603c55604051818152600060208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc573660248360061b830101116102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec5760209160246111ee9201612580565b346102dc5760206003193601126102dc5760043560005260036020526020604060002054604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346102dc5760006003193601126102dc576020604054604051908152f35b346102dc5760806003193601126102dc576104e4611fb36120b1565b60643590604435906024359061231f565b346102dc5760206003193601126102dc576004356001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec5760207ff1649f58e4104d40851477a70db43e4ed6979ae52986533c3bc43abb6642dcfa916120346138c5565b80608055604051908152a1005b346102dc5760006003193601126102dc57612069336000526044602052604060002054151590565b156120875733600052607e6020526020604060002054604051908152f35b7f72a7bddc0000000000000000000000000000000000000000000000000000000060005260046000fd5b600435906001600160a01b03821682036102dc57565b9181601f840112156102dc5782359167ffffffffffffffff83116102dc57602083818601950101116102dc57565b60606003198201126102dc5760043591602435916044359067ffffffffffffffff82116102dc57612128916004016120c7565b9091565b60031960409101126102dc576004359060243590565b919082519283825260005b84811061216e575050601f19601f8460006020809697860101520116010190565b8060208092840101518282860101520161214d565b906020808351928381520192019060005b8181106121a15750505090565b8251845260209384019390920191600101612194565b600411156107bc57565b818102929181159184041417156109e057565b6080810190811067ffffffffffffffff8211176121f057604052565b634e487b7160e01b600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176121f057604052565b60a0810190811067ffffffffffffffff8211176121f057604052565b6040810190811067ffffffffffffffff8211176121f057604052565b90601f601f19910116810190811067ffffffffffffffff8211176121f057604052565b60405191906000835b6018821061229f5750505061229d6103008361225a565b565b6001602081928554815201930191019091612286565b919082018092116109e057565b9060188110156109f65760051b0190565b60188210156109f6570190600090565b80548210156109f65760005260206000209060011b0190600090565b8115612309570490565b634e487b7160e01b600052601260045260246000fd5b6001600160a01b0390939192936123346138c5565b169261234d846000526044602052604060002054151590565b156125005783600052607e602052600160406000200154607d54111561250057836000526045602052612383604060002061227d565b906000936006810290808204600614901517156124ec576123ae916123a7916122b5565b80926122c2565b51806123fa575b50505081600052607e6020526123d160406000209182546122b5565b90557fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b848452607e60205261242361241583600260408820016122d3565b90549060031b1c93846122b5565b92828552607c60205260408520548410156124d8575b905b8382106124825750506124616124789185600052607e60205260026040600020016122d3565b81939154906000199060031b92831b921b19161790565b90553880806123b5565b90936124d060019184600052607c602052611bed6124b1846124a88a60406000206122e3565b500154866121c1565b86600052607c6020526124c88960406000206122e3565b5054906122ff565b94019061243b565b828552607c60205260408520549350612439565b602485634e487b7160e01b81526011600452fd5b50505050565b91908110156109f65760061b0190565b67ffffffffffffffff81116121f05760051b60200190565b9061253882612516565b612545604051918261225a565b828152601f196125558294612516565b0190602036910137565b80518210156109f65760209160051b010190565b919082039182116109e057565b919080156127645760005b81811061271e575061259b6133ee565b6125b2336000526044602052604060002054151590565b80612702575b6126d457916125c68361252e565b600093849285915b808310612689575050506126407f6662f35bbdebb39ebf472c94252ecf0dd0c55fc8d0a922e0595b12c53d643937918561261761260f6126849798876122b5565b606154612573565b60615561262681603d546122b5565b603d55815190604051938493608085526080850190612183565b91602084015285604084015260608301520390a1336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b600190565b909193956126cb6001916126c56126a1888688612506565b916126b26020843594013584613c29565b9190936126bf8b8b61255f565b526122b5565b986122b5565b940191906125ce565b509050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106125b8565b602061272b828487612506565b01351561273a5760010161258b565b7f115e63aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f930363960000000000000000000000000000000000000000000000000000000060005260046000fd5b67ffffffffffffffff81116121f057601f01601f191660200190565b9291926127b68261278e565b916127c4604051938461225a565b8294818452818301116102dc578281602093846000960137010152565b60068210156107bc5752565b93929190916000918315612c5a5761287f916128706128769261286889604051612860816128528c60208301957f0000000000000000000000000000000000000000000000000000000000000000876040919493926060820195825260208201520152565b03601f19810183528261225a565b519020613f07565b9236916127aa565b90614435565b90929192614471565b6001600160a01b0333911603612c32576128976133ee565b6128ae336000526044602052604060002054151590565b80612c18575b612bec576128c1846121b7565b604051936128ce8561223e565b81855260208501946128df826121b7565b81865251946006861015612bd857516128f7816121b7565b612900816121b7565b6040519561290d87612206565b8487526020870181905260408701918252906006821015612bc45751612932816121b7565b612948604051926129428461223e565b836127e1565b612951816121b7565b60208201526060549250855195600460405161296c81612222565b858152602081019889526001600160a01b03604082014281526060830190338252608084019b878d5289600052605f60205260406000209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff000000000000000000000000000000000000000082541617905501965196875160068110156107bc576000987fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612a32816121b7565b612a3b816121b7565b60081b16931691161717905580513388526046602052612a60604089209182546122b5565b90558051612a6d8361421e565b6018811015612bb05791612aac91612a8d612ad3946064019182546122b5565b905551923389526045602052612aa660408a209161421e565b906122d3565b612abf8294925492838360031b1c6122b5565b91906000199060031b92831b921b19161790565b9055612ade336148f1565b612b83575b612aef836061546122b5565b60615560605460018101809111611c2757612684949550606055612b12816121b7565b604051918252826020830152612b27816121b7565b60408201527f8629b9bbe2698bb9733aab9759f36ec9390475da11e15309b08e8755ea5862fd60603392a230336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b338552607e602052612b9a60026040872001613f48565b338552607e602052426001604087200155612ae3565b602489634e487b7160e01b81526032600452fd5b602484634e487b7160e01b81526021600452fd5b602483634e487b7160e01b81526021600452fd5b91925050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b08280a290565b50607d54338252607e6020526001604083200154106128b4565b807f710651b70000000000000000000000000000000000000000000000000000000060049252fd5b6004837f905091fe000000000000000000000000000000000000000000000000000000008152fd5b91908110156109f6576060020190565b9190801561302e5760005b818110612feb5750612cad6133ee565b612cc4336000526044602052604060002054151590565b80612fcf575b6126d457600090612cda8161252e565b93825b828110612d82575050612d42612684939482612d1c7f254fa5b38abff01b7e340b6d943b8e9c17ff5a3f7fe4a447dc3e92ffb3705a0e946060546122b5565b606055612d2b856061546122b5565b606155604051928392606084526060840190612183565b9060208301528460408301520390a130336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b612d8d818484612c82565b93612d9a853580926122b5565b946060813603126102dc5760405191612db283612206565b82526020820190602081013582526040808401910135815260009151906006821015612bd85751612de2816121b7565b612df2604051926129428461223e565b612dfb816121b7565b6020820152612e0c846060546122b5565b9280516004604051612e1d81612222565b868152602081019283526001600160a01b0360408201428152606083019033825260808401958887528a8a52605f60205260408a209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790550190518051906006821015612fbb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612ede816121b7565b612ee7816121b7565b60081b16931691161717905580513384526046602052612f0c604085209182546122b5565b90558051612f198361421e565b6018811015612fa75760019695949392612f5892612f3f612aac936064019182546122b5565b905551923385526045602052612aa6604086209161421e565b9055612f63336148f1565b612f7a575b50612f73828961255f565b5201612cdd565b338152607e602052612f9160026040832001613f48565b338152607e602052836040429220015538612f68565b602485634e487b7160e01b81526032600452fd5b602486634e487b7160e01b81526021600452fd5b50607d5433600052607e60205260016040600020015410612cca565b612ff6818386612c82565b351561300457600101612c9d565b7f905091fe0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f80ef666a0000000000000000000000000000000000000000000000000000000060005260046000fd5b91929092831561273a576130b991612870612876926128686040516020810190612860816128528c8c7f0000000000000000000000000000000000000000000000000000000000000000876040919493926060820195825260208201520152565b6001600160a01b03339116036131cb576130d16133ee565b6130e8336000526044602052604060002054151590565b806131af575b613182576130ff6126849282613c29565b909161310e61260f83856122b5565b60615561311d82603d546122b5565b603d556040519183835260208301527f277a1a847b4895ea9853b0f19969c04b8d17cb55b9ad4504179ede80fbf0f7af60403393a3336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b5050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106130ee565b7f710651b70000000000000000000000000000000000000000000000000000000060005260046000fd5b919082156133c45761324b916128706128769261286860405160208101907f000000000000000000000000000000000000000000000000000000000000000082528860408201526040815261286060608261225a565b6001600160a01b03339116036131cb576132636133ee565b61327a336000526044602052604060002054151590565b806133a8575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206132b2828254612573565b9055603c549080821061332b576132cc8161229d93612573565b603c556040518181527fecf9667816c0bdb34ff25e5437430e488cebc3841c3a2c90e3927e07afcd78b460203392a2336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b7f4aa330410000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa689c4650000000000000000000000000000000000000000000000000000000060005260046000fd5b50337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2565b50607d5433600052607e60205260016040600020015410613280565b7f8f9ba5d50000000000000000000000000000000000000000000000000000000060005260046000fd5b6133f66138c5565b61340d336000526044602052604060002054151590565b1561229d5733600052607e602052600160406000200154607d54111561229d57336000526045602052613443604060002061227d565b60009060029060005b600481106134b95750506002146134a1575b33600052607e60205261347760406000209182546122b5565b9055337fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b33600052607e6020524260016040600020015561345e565b60005b600681106134cd575060010161344c565b60068202828104600614831517156109e057816134e9916122b5565b6134f381856122c2565b519081156135d45733600052607e6020526135158160026040600020016122d3565b90549060031b1c916102dc83018084116109e0576000838152607c602052604090205481106135ca575081600052607c602052604060002054925b905b8382106135855750509061357c6124616001949333600052607e60205260026040600020016122d3565b90555b016134bc565b90976135c2600191611bed6135ab846124a88e89600052607c60205260406000206122e3565b86600052607c6020526124c88d60406000206122e3565b980190613552565b6001975092613550565b505060019061357f565b33600052603f60205260406000206040516135f8816121d4565b815481526060600183015491602081019283526003600285015494604083019586520154918291015260009080421161375c575b5090816136cd575b50505033600052603f60205280604060002054106136c7576126849033600052603f6020526040600020613669828254612573565b90556040518181527fcf93002ecae3f252116b013a1ae8ec072919c19ab134261c065cc94d5b0b3e5860203392a2336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b50600090565b518082101561375557505b8062015180029162015180830482036109e0576137329233600052603f60205261370b60036040600020019182546122b5565b905533600052603f6020526001604060002001613729838254612573565b905551906121c1565b33600052603f60205261374b60406000209182546122b5565b9055388080613634565b90506136d8565b61376a620151809142612573565b04906001820180921161377e57503861362c565b80634e487b7160e01b602492526011600452fd5b80156133c4576137a06133ee565b6137b7336000526044602052604060002054151590565b80613866575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206137ef828254612573565b9055603c549080821061332b576138098161229d93612573565b603c557f916496170aac2ab5c6b8c47b015f9a422bab4f77114f70a76c7289a2e7312eb46020604051838152a1336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b50607d5433600052607e602052600160406000200154106137bd565b9060405161388f8161223e565b602060ff8294546138a2828216856127e1565b60081c16916138b0836121b7565b0152565b60ff1660ff81146109e05760010190565b60625460011461229d57606354421061229d576080541561229d576138e8613f96565b620151806138f860635442612573565b04600101806001116109e057600060189261391284612516565b90613920604051928361225a565b848252601f1961392f86612516565b0136602084013760405191606484845b888210613c12575050506139556103008461225a565b835b60ff8116946004861015613a365795926000906006870298878a0460061488151715985b60ff84166006811015613a19578a6109e057613997818d6122b5565b908a8a6139a484826122c2565b5115613a0b57926127106139f56126bf946139e0613a0598956139ca866139ff9a6122c2565b51906000526004602052604060002054906121c1565b906000526003602052604060002054906121c1565b049283918b61255f565b936138b4565b9261397b565b5050505092613a05906138b4565b50959750985092965050613a2c906138b4565b9490919394613957565b919592969450508015613be0576080549160005b60ff81166004811015613b8257801560068281029283041417159860005b60ff81166006811015613b6f578b6109e0578787613a91613a8a8e94886122b5565b809461255f565b518015613b625790613ac6613acb92613ac18b60405196613ab18861223e565b60008852600060208901526121c1565b6121c1565b6122ff565b9060208101918252613add838c6122c2565b51815282600052607c60205260406000208054680100000000000000008110156121f057613b10916001820181556122e3565b929092613b4c5760019151835551910155898110156109f6576047019081549160001983146109e0576001613b47930190556138b4565b613a68565b634e487b7160e01b600052600060045260246000fd5b50505050613b47906138b4565b5050985050613b7d906138b4565b613a4a565b505050505050505042607d55613b9d62015180420642612573565b6201518081018091116109e057607f54613bb6916122b5565b6063557f040bde66f3369b7fc094e7401b5de26a5be6900e0fb645651d59eb57b8620dbc600080a1565b5050505050613bf462015180420642612573565b6201518081018091116109e057607f54613c0d916122b5565b606355565b82548152600192830192919091019060200161393f565b919091613c3461416c565b80600052605f6020526040600020906001600160a01b0360038301541680156107d25784600184015410613e2e573303613e045760009380926004600282015491019060ff825460081c1690613c89826121b7565b816000526002602052604060002054620151808102908082046201518014901517156109e057613cb99042612573565b10613dc9575b503360005260466020526040600020613cd9838254612573565b9055613cec613ce782613882565b61421e565b60188110156109f657613d3b91613d2791606401613d0b858254612573565b9055336000526045602052612aa6613ce7604060002092613882565b8192915490612abf85838360031b1c612573565b905533600052604660205260406000205415613db7575b81600052605f602052613d6e6001604060002001918254612573565b905580600052605f60205260016040600020015415613d8c57509190565b600052605f602052600060046040822082815582600182015582600282015582600382015501559190565b613dc03361478c565b613d5257600080fd5b9195509250613dd7816121b7565b6000526005602052612710613df1604060002054866121c1565b0493613dfd8582612573565b9238613cbf565b7f387224d40000000000000000000000000000000000000000000000000000000060005260046000fd5b7fe0a6f7200000000000000000000000000000000000000000000000000000000060005260046000fd5b61229d926001600160a01b03604051937fa9059cbb000000000000000000000000000000000000000000000000000000006020860152166024840152604483015260448252613ea860648361225a565b61425f565b9091926001600160a01b0361229d9481604051957f23b872dd000000000000000000000000000000000000000000000000000000006020880152166024860152166044840152606483015260648252613ea860848361225a565b604290613f12614319565b90604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b60478114613f7f57604790604754906000915b60188310613f695750505050565b6001809194019283549481840155019192613f5b565b50565b6001600160a01b0360005416330361141957565b6031547f0000000000000000000000000000000000000000000000000000000000000000801562278d0080830292830414171592915b60325481146140d05760068110156109f6576003810284613ff1600983015442612573565b906109e0578310156140425760009060088101549150600701546000526003602052604060002055603154600181018091116109e05760069006603155600181018091116109e05760069006613fcc565b505091905b6033545b60345481146140ca5760048110156109f6576003810283614070601b83015442612573565b906109e0578510156140c357600090601a81015491506019015460005260046020526040600020556033546001810181116109e0576001600491086033556001810181116109e05760016004910861404b565b5050915050565b50915050565b509190614047565b801561414257604054106140e857565b6001600160a01b03600054163314158061412d575b61410357565b7f1c2a55280000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b03603b54163314156140fd565b7f11184a7b0000000000000000000000000000000000000000000000000000000060005260046000fd5b6035547f00000000000000000000000000000000000000000000000000000000000000009162278d0083029280840462278d001490151715915b60365481146140ca5760048110156109f65760038102836141cb602783015442612573565b906109e0578510156140c357600090602681015491506025015460005260056020526040600020556035546001810181116109e0576001600491086035556001810181116109e0576001600491086141a6565b60208101519061422d826121b7565b614236826121b7565b6006820291808304600614901517156109e0575160068110156107bc5761425c916122b5565b90565b6000806001600160a01b036142aa93169360208151910182865af13d15614311573d9061428b8261278e565b91614299604051938461225a565b82523d6000602084013e5b8361494b565b80519081151591826142ed575b50506142c05750565b7f5274afe70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b81925090602091810103126102dc57602001518015908115036102dc5738806142b7565b6060906142a4565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630148061440c575b15614374577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a0815261440660c08261225a565b51902090565b507f0000000000000000000000000000000000000000000000000000000000000000461461434b565b81519190604183036144665761445f92506020820151906060604084015193015160001a9061485c565b9192909190565b505060009160029190565b61447a816121b7565b80614483575050565b61448c816121b7565b600181036144be577ff645eedf0000000000000000000000000000000000000000000000000000000060005260046000fd5b6144c7816121b7565b600281036144fd57507ffce698f70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b600390614509816121b7565b146145115750565b7fd78bce0c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60ff81146145a25760ff811690601f8211614578576040805192614562828561225a565b60208452601f1960208501920136833783525290565b7fb3512b0c0000000000000000000000000000000000000000000000000000000060005260046000fd5b506040516000604154908160011c91600181168015614698575b60208410811461468457838552849291811561464757506001146145e7575b61425c9250038261225a565b506041600090815290917f7c9785e8241615bc80415d89775984a1337d15dc1bf4ce50f41988b2a2b336a75b81831061462b57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614613565b6020925061425c9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016145db565b602483634e487b7160e01b81526022600452fd5b92607f16926145bc565b60ff81146146c65760ff811690601f8211614578576040805192614562828561225a565b506040516000604254908160011c9160018116801561476a575b602084108114614684578385528492918115614647575060011461470a5761425c9250038261225a565b506042600090815290917f38dfe4635b27babeca8be38d3b448cb5161a639b899a14825ba9c8d7892eb8c35b81831061474e57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614736565b92607f16926146e0565b80548210156109f65760005260206000200190600090565b60008181526044602052604090205480156148555760001981018181116109e0576043549060001982019182116109e05781810361481b575b505050604354801561480557600019016147e0816043614774565b60001982549160031b1b19169055604355600052604460205260006040812055600190565b634e487b7160e01b600052603160045260246000fd5b61483d61482c612461936043614774565b90549060031b1c9283926043614774565b905560005260446020526040600020553880806147c5565b5050600090565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116148e5579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa156148d9576000516001600160a01b038116156148cd5790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b50505060009160039190565b806000526044602052604060002054156000146136c757604354680100000000000000008110156121f0576149326124618260018594016043556043614774565b9055604354906000526044602052604060002055600190565b9061498a575080511561496057805190602001fd5b7f1425ea420000000000000000000000000000000000000000000000000000000060005260046000fd5b815115806149d2575b61499b575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b50803b1561499356fea264697066735822122029bc36c2c231c119492b3ac9d5378cd3e143f3f82f63c8339cc7048bd177894764736f6c634300081a00336101606040523461051257604051610018604082610517565b6008815260208101672d27222a37b5b2b760c11b81526040519061003d604083610517565b60088252672d27222a37b5b2b760c11b602083015260405192610061604085610517565b600384526216939160ea1b602085015260405193610080604086610517565b600185526020850193603160f81b855233156104fc57600680546001600160a01b03199081169091556000805433928116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a38051906001600160401b0382116103f95760045490600182811c921680156104f2575b60208310146103d95781601f849311610482575b50602090601f831160011461041a5760009261040f575b50508160011b916000199060031b1c1916176004555b8051906001600160401b0382116103f95760055490600182811c921680156103ef575b60208310146103d95781601f849311610385575b50602090601f831160011461031d57600092610312575b50508160011b916000199060031b1c1916176005555b6101b48161053a565b610120526101c1846106e8565b61014052519020918260e05251902080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261022a60c082610517565b5190206080523060c05260006003546b024306c4097859c43c00000081018091116102fe576003555033600052600160205260406000206b024306c4097859c43c00000081540190556040516b024306c4097859c43c000000815260007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203393a36040516113c69081610887823960805181610efe015260a05181610fbb015260c05181610ecf015260e05181610f4d01526101005181610f730152610120518161058b015261014051816105b40152f35b634e487b7160e01b82526011600452602482fd5b015190503880610195565b600560009081528281209350601f198516905b81811061036d5750908460019594939210610354575b505050811b016005556101ab565b015160001960f88460031b161c19169055388080610346565b92936020600181928786015181550195019301610330565b90915060056000526020600020601f840160051c810191602085106103cf575b90601f859493920160051c01905b8181106103c0575061017e565b600081558493506001016103b3565b90915081906103a5565b634e487b7160e01b600052602260045260246000fd5b91607f169161016a565b634e487b7160e01b600052604160045260246000fd5b015190503880610131565b600460009081528281209350601f198516905b81811061046a5750908460019594939210610451575b505050811b01600455610147565b015160001960f88460031b161c19169055388080610443565b9293602060018192878601518155019501930161042d565b60046000529091507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f840160051c810191602085106104e8575b90601f859493920160051c01905b8181106104d9575061011a565b600081558493506001016104cc565b90915081906104be565b91607f1691610106565b631e4fbdf760e01b600052600060045260246000fd5b600080fd5b601f909101601f19168101906001600160401b038211908210176103f957604052565b908151602081106000146105d2575090601f815111610576576020815191015160208210610566571790565b6000198260200360031b1b161790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b8381106105ba5750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001610598565b6001600160401b0381116103f957600754600181811c911680156106de575b60208210146103d957601f81116106a8575b50602092601f82116001146106435792819293600092610638575b50508160011b916000199060031b1c19161760075560ff90565b01519050388061061e565b601f198216936007600052806000209160005b8681106106905750836001959610610677575b505050811b0160075560ff90565b015160001960f88460031b161c19169055388080610669565b91926020600181928685015181550194019201610656565b6007600052601f6020600020910160051c810190601f830160051c015b8181106106d25750610603565b600081556001016106c5565b90607f16906105f1565b90815160208110600014610770575090601f815111610714576020815191015160208210610566571790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b8381106107585750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001610736565b6001600160401b0381116103f957600854600181811c9116801561087c575b60208210146103d957601f8111610846575b50602092601f82116001146107e157928192936000926107d6575b50508160011b916000199060031b1c19161760085560ff90565b0151905038806107bc565b601f198216936008600052806000209160005b86811061082e5750836001959610610815575b505050811b0160085560ff90565b015160001960f88460031b161c19169055388080610807565b919260206001819286850151815501940192016107f4565b6008600052601f6020600020910160051c810190601f830160051c015b81811061087057506107a1565b60008155600101610863565b90607f169061078f56fe608080604052600436101561001357600080fd5b60003560e01c90816306fdde031461095257508063095ea7b31461092c57806318160ddd1461090e57806323b872dd146108d6578063313ce567146108ba5780633644e5151461089757806342966c681461087a57806370a0823114610833578063715018a6146107f057806379ba50971461070257806379cc6790146106d25780637ecebe001461068b57806384b0196e146105725780638da5cb5b1461053e578063902d55a51461051f57806395d89b411461041c578063a9059cbb146103eb578063d505accf14610252578063dd62ed3e146101e0578063e30c3978146101ac5763f2fde38b1461010657600080fd5b346101a75760206003193601126101a75773ffffffffffffffffffffffffffffffffffffffff610134610a56565b61013c61108a565b16807fffffffffffffffffffffffff0000000000000000000000000000000000000000600654161760065573ffffffffffffffffffffffffffffffffffffffff600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b600080fd5b346101a75760006003193601126101a757602073ffffffffffffffffffffffffffffffffffffffff60065416604051908152f35b346101a75760406003193601126101a7576101f9610a56565b73ffffffffffffffffffffffffffffffffffffffff610216610a79565b9116600052600260205273ffffffffffffffffffffffffffffffffffffffff604060002091166000526020526020604060002054604051908152f35b346101a75760e06003193601126101a75761026b610a56565b610273610a79565b604435906064359260843560ff811681036101a7578442116103bd5761037661036d73ffffffffffffffffffffffffffffffffffffffff92838516978860005260096020526040600020908154916001830190556040519060208201927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c984528b6040840152878a1660608401528a608084015260a083015260c082015260c0815261032060e082610b91565b51902061032b610eb8565b90604051917f190100000000000000000000000000000000000000000000000000000000000083526002830152602282015260c43591604260a4359220611202565b909291926112a4565b1684810361038b575061038993506110ab565b005b84907f4b800e460000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b847f627913020000000000000000000000000000000000000000000000000000000060005260045260246000fd5b346101a75760406003193601126101a757610411610407610a56565b6024359033610d8b565b602060405160018152f35b346101a75760006003193601126101a757604051600060055461043e81610a9c565b80845290600181169081156104dd575060011461047e575b61047a8361046681850382610b91565b6040519182916020835260208301906109f7565b0390f35b600560009081527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0939250905b8082106104c357509091508101602001610466610456565b9192600181602092548385880101520191019092916104ab565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208086019190915291151560051b840190910191506104669050610456565b346101a75760006003193601126101a75760206040516329b927008152f35b346101a75760006003193601126101a757602073ffffffffffffffffffffffffffffffffffffffff60005416604051908152f35b346101a75760006003193601126101a75761062d6105af7f000000000000000000000000000000000000000000000000000000000000000061112c565b6105d87f00000000000000000000000000000000000000000000000000000000000000006111cb565b602061063b604051926105eb8385610b91565b6000845260003681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e08701906109f7565b9085820360408701526109f7565b466060850152306080850152600060a085015283810360c085015281808451928381520193019160005b82811061067457505050500390f35b835185528695509381019392810192600101610665565b346101a75760206003193601126101a75773ffffffffffffffffffffffffffffffffffffffff6106b9610a56565b1660005260096020526020604060002054604051908152f35b346101a75760406003193601126101a7576103896106ee610a56565b602435906106fd823383610c01565b610fe1565b346101a75760006003193601126101a7576006543373ffffffffffffffffffffffffffffffffffffffff8216036107c2577fffffffffffffffffffffffff000000000000000000000000000000000000000016600655600054337fffffffffffffffffffffffff000000000000000000000000000000000000000082161760005573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b346101a75760006003193601126101a75761080961108a565b7f343916260000000000000000000000000000000000000000000000000000000060005260046000fd5b346101a75760206003193601126101a75773ffffffffffffffffffffffffffffffffffffffff610861610a56565b1660005260016020526020604060002054604051908152f35b346101a75760206003193601126101a75761038960043533610fe1565b346101a75760006003193601126101a75760206108b2610eb8565b604051908152f35b346101a75760006003193601126101a757602060405160128152f35b346101a75760606003193601126101a7576104116108f2610a56565b6108fa610a79565b60443591610909833383610c01565b610d8b565b346101a75760006003193601126101a7576020600354604051908152f35b346101a75760406003193601126101a757610411610948610a56565b60243590336110ab565b346101a75760006003193601126101a757600060045461097181610a9c565b80845290600181169081156104dd57506001146109985761047a8361046681850382610b91565b600460009081527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b939250905b8082106109dd57509091508101602001610466610456565b9192600181602092548385880101520191019092916109c5565b919082519283825260005b848110610a415750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201610a02565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101a757565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036101a757565b90600182811c92168015610ae5575b6020831014610ab657565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691610aab565b60009291815491610aff83610a9c565b8083529260018116908115610b555750600114610b1b57505050565b60009081526020812093945091925b838310610b3b575060209250010190565b600181602092949394548385870101520191019190610b2a565b905060209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091509291921683830152151560051b010190565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610bd257604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff909291921691826000526002602052604060002073ffffffffffffffffffffffffffffffffffffffff8216600052602052604060002054927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8403610c7c575b50505050565b828410610d3f578015610d105773ffffffffffffffffffffffffffffffffffffffff821615610ce157600052600260205273ffffffffffffffffffffffffffffffffffffffff6040600020911660005260205260406000209103905538808080610c76565b7f94280d6200000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b7fe602df0500000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b5073ffffffffffffffffffffffffffffffffffffffff83917ffb8f41b2000000000000000000000000000000000000000000000000000000006000521660045260245260445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff16908115610e895773ffffffffffffffffffffffffffffffffffffffff16918215610e5a57600082815260016020526040812054828110610e275791604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9587602096526001865203828220558681526001845220818154019055604051908152a3565b6064937fe450d38c0000000000000000000000000000000000000000000000000000000083949352600452602452604452fd5b7fec442f0500000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b7f96c6fd1e00000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016301480610fb8575b15610f20577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a08152610fb260c082610b91565b51902090565b507f00000000000000000000000000000000000000000000000000000000000000004614610ef7565b73ffffffffffffffffffffffffffffffffffffffff168015610e89576000918183526001602052604083205481811061105857817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092858752600184520360408620558060035403600355604051908152a3565b83927fe450d38c0000000000000000000000000000000000000000000000000000000060649552600452602452604452fd5b73ffffffffffffffffffffffffffffffffffffffff6000541633036107c257565b73ffffffffffffffffffffffffffffffffffffffff16908115610d105773ffffffffffffffffffffffffffffffffffffffff16918215610ce15760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260028252604060002085600052825280604060002055604051908152a3565b60ff81146111ae5760ff811690601f82116111845760408051926111508285610b91565b602084527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208501920136833783525290565b7fb3512b0c0000000000000000000000000000000000000000000000000000000060005260046000fd5b506040516111c8816111c1816007610aef565b0382610b91565b90565b60ff81146111ef5760ff811690601f82116111845760408051926111508285610b91565b506040516111c8816111c1816008610aef565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411611298579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa1561128c5760005173ffffffffffffffffffffffffffffffffffffffff8116156112805790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b50505060009160039190565b919091600481101561136157806112ba57509050565b6000600182036112ee577ff645eedf0000000000000000000000000000000000000000000000000000000060005260046000fd5b506002810361132557827ffce698f70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b9091600360009214611335575050565b602492507fd78bce0c000000000000000000000000000000000000000000000000000000008252600452fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea2646970667358221220a60629b6927bbe3a89e220d5a83a29e9b89672a00d099ce3d4418135d052111564736f6c634300081a0033000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d812885994300000000000000000000000036ac695293d35bd90586b5353471c2004407ef6e00000000000000000000000041c31f690873ec5a642dd374e2f2dd1528d057300000000000000000000000000000000000000000000007b864634d3cc768000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6d34ae9275e1a900000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000036fdb8800968fb8b38d53ebce3c86f909c79637200000000000000000000000000000000000000000031ef6e017e6033e9a00000000000000000000000000000000000000000000000000000000000000000016800000000000000000000000000000000000000000000000000000000013c680000000000000000000000000000000000000000000006cf2f721f56aca1600000000000000000000000000000552002ec9504f3931019d63a8d992c98fcdf1eb70000000000000000000000000000000000000000003b41ea57328adb16b00000000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000000000000000000000000000000000000000ed4e0000000000000000000000000000000000000000000009a58bb5318a10ffd00000000000000000000000000000719d74be74980f94b2b9ee670f94f540661f9f6600000000000000000000000000000000000000000004a1de0780a6934f100000000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000076a700000000000000000000000000000000000000000000012870bb26f3ec17f000000000000000000000000000004c5e31774b09cf489c482a472bb78ae8c024c70100000000000000000000000000000000000000000003e41c59d913dabc00000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000bdbc41e0348b300000000000000000000000000000003ce0935e2f28339d6f9f1068356a90bf6d3173e3000000000000000000000000000000000000000000045348a87272a44a20000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000e2cb06bdfece5f6000000000000000000000000000009ff1299f5abea9ff1789c86e1c972ac1b3a8d38d0000000000000000000000000000000000000000000445f16bd0aef280c0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000004f1a0000000000000000000000000000000000000000000000f022435fc28028c00000000000000000000000000000ca4bde17f2a29fd041665cee2d206bc5c2ae507f00000000000000000000000000000000000000000004389a2f2eeb40b760000000000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000fd7980018631f2200000000000000000000000000000ae0a2dbdfdc0b9d1132b52796d5fcc9a19bd5dc300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004a1d89bb94865ec00000000000000000000000000000053423085f352a12f0b4c51e00c512e130ae02b3800000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000004a1d89bb94865ec000000000000000000000000000000af3bf534d2b026b9c72cb6fb30a5f7f879a6f4f1000000000000000000000000000000000000000000a7eaa0281559f34020000000000000000000000000000000000000000000000000000000000000000007080000000000000000000000000000000000000000000000000000000003b5380000000000000000000000000000000000000000000000004be4e7267b6ae00000000000000000000000000000344b16fe768582f69205897d240e2971b3ac0c09000000000000000000000000000000000000000000250ec5f358893873d0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000943b021e44ac2c4300000000000000000000000000000c5d2d690d683c6f7bb654e91fed293caec2a26b50000000000000000000000000000000000000000001851a42ee173aa2940000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000004a1e59e6490d2d9c0000000000000000000000000000018bf7f1cf41f840a7cb21618d5d1c1af7ca3e9080000000000000000000000000000000000000000002e524435ac3e59a8a0000000000000000000000000000000000000000000000000000000000000000004380000000000000000000000000000000000000000000000000000000001da9c00000000000000000000000000000000000000000000000031df9095a18f600000000000000000000000000000fdf5b0d1cd40223ef91027ce014cb4a31ac4ecf200000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000001bccb3fbc0c0014000000
Deployed Bytecode
0x6080604052600436101561001257600080fd5b60003560e01c806309a69f57146120415780630aa4353314611fc457806314596a8214611f9757806315e3a6dc14611f7957806320dd50df14611f3557806321ea1bc414611efa57806322c45b6714611ece5780632420865714611e3d5780632c4eb8b214611db35780632e4edf1714611d65578063301a3e9514611cfe57806334238dd814611c59578063370f82fe14611c3b578063428d0b2514611b4657806342ba8c8914611aa057806342c9b7dd14611a6557806346b9c06914611a2a57806348efbf33146119e65780634abdf2e0146119985780634ec1b85814611971578063549013be14611705578063564ac5f514611674578063567b60be14611648578063656e84301461161c578063663ab7e1146115ce57806366ba3392146115b0578063677e0b271461151957806367936d13146114de5780636f4a2cd0146114c5578063715018a614611482578063725decc41461144757806379ba5097146113735780637ef6e8bd1461134c57806380e5deb01461131157806384b0196e1461121f5780638da5cb5b146111f85780638f97e3a0146111d05780639043ec6a146110da57806391eedf39146110a8578063990717441461106d5780639c82e39014610fc75780639f91c58d14610f9b578063abde825414610f11578063b03b65c414610e41578063b1b2f6eb14610df5578063b7fd4a6714610dd7578063b80adc5c14610d9c578063c04a6a4f14610b36578063ca26930014610afb578063cc37f730146107fc578063ce325bf8146106b4578063d179bd2214610696578063dad74b3f1461065b578063dc9139761461063d578063e30c397814610616578063e49f858a14610582578063ef772a6014610564578063f2b6c74914610418578063f2fde38b146102e15763fc65baaf146102b057600080fd5b346102dc5760206003193601126102dc5760043560005260026020526020604060002054604051908152f35b600080fd5b346102dc5760206003193601126102dc576001600160a01b036103026120b1565b61030a613f82565b1680156103ee576001600160a01b03603b541681146103c4576001600160a01b03603a5416811461039a5761033d613f82565b807fffffffffffffffffffffffff000000000000000000000000000000000000000060015416176001556001600160a01b03600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b7f939cd4610000000000000000000000000000000000000000000000000000000060005260046000fd5b7f9249f1750000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa4dd273e0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc576104316120b1565b6024356001600160a01b03603a54163314158061054f575b8061053a575b6105105761045c816140d8565b603e54918282116104e657610474826104e494612573565b603e55604051828152600160208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b005b7f6643a25f0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f2636942f0000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b036000541633141561044f565b506001600160a01b03603b5416331415610449565b346102dc5760006003193601126102dc576020606154604051908152f35b346102dc5760206003193601126102dc5761059b6120b1565b6001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576001600160a01b0316600052607e6020526020604060002054604051908152f35b7f8b2731aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206001600160a01b0360015416604051908152f35b346102dc5760006003193601126102dc576020603c54604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc576020603e54604051908152f35b346102dc5760206003193601126102dc576040516106d181612222565b600081526000602082015260006040820152600060608201526080604051916106f98361223e565b60008352600060208401520152600435600052605f60205260406000206001600160a01b036003820154169081156107d2576001600160a01b036040519261074084612222565b82548452600183015492602085019384526107706004600283015492604088019384526060880194855201613882565b936080860194855260405195518652516020860152516040850152511660608301525190815160068110156107bc5760c092602091608084015201516107b5816121b7565b60a0820152f35b634e487b7160e01b600052602160045260246000fd5b7fd9e125150000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5761080a3661212c565b906001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576004811015610ad15780600052603960205261085c60406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000003908162278d00029162278d008304036109e05710610aa75761089d61416c565b8060005260056020526040600020548211600014610a8a578060005260056020526108cd60406000205483612573565b8160005260066020526040600020548311908115610a60575b8115610a36575b50610a0c578060005260396020524260406000205560405161090e81612206565b8181528260208201524260408201526036549060048210156109f657600361094f920260250190604060029180518455602081015160018501550151910155565b603654906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086036558160005260056020526040600020546040519260808452600a60808501527f50656e616c74794665650000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b7f573a59ba0000000000000000000000000000000000000000000000000000000060005260046000fd5b90507f000000000000000000000000000000000000000000000000000000000000000110836108ed565b7f0000000000000000000000000000000000000000000000000000000000000000841091506108e6565b806000526005602052610aa282604060002054612573565b6108cd565b7fc2c1e97b0000000000000000000000000000000000000000000000000000000060005260046000fd5b7ffc87de8c0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000648152f35b346102dc57610b443661212c565b906001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576006811015610ad157806000526037602052610b9660406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000003908162278d00029162278d008304036109e05710610aa757610bd7613f96565b8060005260036020526040600020548211600014610d7f57806000526003602052610c0760406000205483612573565b7f00000000000000000000000000000000000000000000000000000000000003e88311908115610d55575b8115610d2b575b50610a0c5780600052603760205242604060002055604051610c5a81612206565b8181528260208201524260408201526032549060068210156109f6576003610c9b920260070190604060029180518455602081015160018501550151910155565b60325490600182018092116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f092600660c093066032558160005260036020526040600020546040519260808452600960808501527f54696572426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f00000000000000000000000000000000000000000000000000000000000000011083610c39565b7f000000000000000000000000000000000000000000000000000000000000006484109150610c32565b806000526003602052610d9782604060002054612573565b610c07565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc576020606054604051908152f35b346102dc5760206003193601126102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576104e4600435613792565b346102dc5760206003193601126102dc576001600160a01b03610e626120b1565b610e6a613f82565b1680156103ee576001600160a01b0360005416811461039a576001600160a01b03603b54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603a541617603a55337f6d0f78b3a4ae2472e08ee885ed47bfb1ace996c12d6ff019537c99821244de8d600080a3005b7fd06f5c380000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576104e4600435610f3481603e546122b5565b603e55604051818152600160208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b346102dc5760206003193601126102dc5760043560005260046020526020604060002054604051908152f35b346102dc5760206003193601126102dc576001600160a01b03610fe86120b1565b610ff0613f82565b1680156103ee576001600160a01b036000541681146103c4576001600160a01b03603a54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603b541617603b55337f786bbb915c47f5cb032b19e70cd81f673f1ee5c1ac711db6681470264d521125600080a3005b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc5733600052607e6020526020600160406000200154607d541115604051908152f35b346102dc5760406003193601126102dc576110f36120b1565b6024356001600160a01b03603a5416331415806111bb575b806111a6575b6105105761111e816140d8565b603d54918282116104e657611136826104e494612573565b603d55604051828152600260208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b506001600160a01b0360005416331415611111565b506001600160a01b03603b541633141561110b565b346102dc5760206003193601126102dc5760206111ee6004356135de565b6040519015158152f35b346102dc5760006003193601126102dc5760206001600160a01b0360005416604051908152f35b346102dc5760006003193601126102dc5760e061125b7f5a4e445374616b696e670000000000000000000000000000000000000000000a61453e565b61130d6112877f31000000000000000000000000000000000000000000000000000000000000016146a2565b916112ec6020936112de6040519361129f878661225a565b6000855260003681376040519788977f0f00000000000000000000000000000000000000000000000000000000000000895288015260e0870190612142565b908582036040870152612142565b90466060850152306080850152600060a085015283820360c0850152612183565b0390f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc5760206001600160a01b03603b5416604051908152f35b346102dc5760006003193601126102dc57600154336001600160a01b03821603611419577fffffffffffffffffffffffff000000000000000000000000000000000000000016600155600054337fffffffffffffffffffffffff00000000000000000000000000000000000000008216176000556001600160a01b033391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000648152f35b346102dc5760006003193601126102dc5761149b613f82565b7f23dbc96b0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576104e46133ee565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000038152f35b346102dc5760406003193601126102dc5760243567ffffffffffffffff81116102dc5761154a9036906004016120c7565b6001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943163314611586576104e4916004356131f5565b7f163156450000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576020608054604051908152f35b346102dc576115dc366120f5565b906001600160a01b039392937f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943163314611586576020936111ee93613058565b346102dc5760206003193601126102dc5760043560005260066020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043560005260056020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc5736602460608402830101116102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec5760209160246111ee9201612c92565b346102dc576117133661212c565b906001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576004811015610ad15780600052603860205261176560406000205442612573565b7f00000000000000000000000000000000000000000000000000000000000000039062278d0082029180830462278d0014901517156109e05710610aa7576117ab613f96565b8060005260046020526040600020548211600014611954578060005260046020526117db60406000205483612573565b7f00000000000000000000000000000000000000000000000000000000000003e8831190811561192a575b8115611900575b50610a0c578060005260386020524260406000205560405161182e81612206565b8181528260208201524260408201526034549060048210156109f657600361186f920260190190604060029180518455602081015160018501550151910155565b603454906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086034558160005260046020526040600020546040519260808452600960808501527f506c616e426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f0000000000000000000000000000000000000000000000000000000000000001108361180d565b7f000000000000000000000000000000000000000000000000000000000000006484109150611806565b80600052600460205261196c82604060002054612573565b6117db565b346102dc5760006003193601126102dc5760206001600160a01b03603a5416604051908152f35b346102dc5760206003193601126102dc576004356119b4613f82565b806040556040519081527f075e0c6585fd90d164d2f7f5d5399e97900ed892807821f47be4d64c9eb5238160203392a2005b346102dc5760006003193601126102dc5760206040516001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d168152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000003e88152f35b346102dc5760206003193601126102dc576004356001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec57606154611b1c576020817fe056d7ccce0301fe61ac060fe377a3c89b179cd09fa62ef48618d5791a78d64892607f55604051908152a1005b7fac5cadc30000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576001600160a01b03611b676120b1565b16600052603f6020526060604060002060405190611b84826121d4565b80549081835260018101549260208101938452600360028301549260408301938452015494859101528192600094804211611c04575b509360209481611bcf575b8585604051908152f35b51611bf39450611bed92919080821015611bfc5750905b51906121c1565b906122b5565b82808080611bc5565b905090611be6565b611c12620151809142612573565b0460018101809111611c275794506020611bba565b602486634e487b7160e01b81526011600452fd5b346102dc5760006003193601126102dc576020603d54604051908152f35b346102dc5760006003193601126102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec57606254600214611cd4576002606255426063557faeb375a1784645db3075ba87abdf2952280755ad1967e93aea416f217386a1bb600080a1005b7ff62324030000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc5760043560068110156102dc576024359060048210156102dc57611d31826121b7565b6006820291808304600614901517156109e057611d4d916122b5565b60188110156109f65760209060640154604051908152f35b346102dc57611d73366120f5565b906001600160a01b039392937f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943163314611586576020936111ee936127ed565b346102dc5760206003193601126102dc576104e4600435611dd681603c546122b5565b603c55604051818152600060208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc573660248360061b830101116102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec5760209160246111ee9201612580565b346102dc5760206003193601126102dc5760043560005260036020526020604060002054604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000003e88152f35b346102dc5760006003193601126102dc5760206040516001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943168152f35b346102dc5760006003193601126102dc576020604054604051908152f35b346102dc5760806003193601126102dc576104e4611fb36120b1565b60643590604435906024359061231f565b346102dc5760206003193601126102dc576004356001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec5760207ff1649f58e4104d40851477a70db43e4ed6979ae52986533c3bc43abb6642dcfa916120346138c5565b80608055604051908152a1005b346102dc5760006003193601126102dc57612069336000526044602052604060002054151590565b156120875733600052607e6020526020604060002054604051908152f35b7f72a7bddc0000000000000000000000000000000000000000000000000000000060005260046000fd5b600435906001600160a01b03821682036102dc57565b9181601f840112156102dc5782359167ffffffffffffffff83116102dc57602083818601950101116102dc57565b60606003198201126102dc5760043591602435916044359067ffffffffffffffff82116102dc57612128916004016120c7565b9091565b60031960409101126102dc576004359060243590565b919082519283825260005b84811061216e575050601f19601f8460006020809697860101520116010190565b8060208092840101518282860101520161214d565b906020808351928381520192019060005b8181106121a15750505090565b8251845260209384019390920191600101612194565b600411156107bc57565b818102929181159184041417156109e057565b6080810190811067ffffffffffffffff8211176121f057604052565b634e487b7160e01b600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176121f057604052565b60a0810190811067ffffffffffffffff8211176121f057604052565b6040810190811067ffffffffffffffff8211176121f057604052565b90601f601f19910116810190811067ffffffffffffffff8211176121f057604052565b60405191906000835b6018821061229f5750505061229d6103008361225a565b565b6001602081928554815201930191019091612286565b919082018092116109e057565b9060188110156109f65760051b0190565b60188210156109f6570190600090565b80548210156109f65760005260206000209060011b0190600090565b8115612309570490565b634e487b7160e01b600052601260045260246000fd5b6001600160a01b0390939192936123346138c5565b169261234d846000526044602052604060002054151590565b156125005783600052607e602052600160406000200154607d54111561250057836000526045602052612383604060002061227d565b906000936006810290808204600614901517156124ec576123ae916123a7916122b5565b80926122c2565b51806123fa575b50505081600052607e6020526123d160406000209182546122b5565b90557fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b848452607e60205261242361241583600260408820016122d3565b90549060031b1c93846122b5565b92828552607c60205260408520548410156124d8575b905b8382106124825750506124616124789185600052607e60205260026040600020016122d3565b81939154906000199060031b92831b921b19161790565b90553880806123b5565b90936124d060019184600052607c602052611bed6124b1846124a88a60406000206122e3565b500154866121c1565b86600052607c6020526124c88960406000206122e3565b5054906122ff565b94019061243b565b828552607c60205260408520549350612439565b602485634e487b7160e01b81526011600452fd5b50505050565b91908110156109f65760061b0190565b67ffffffffffffffff81116121f05760051b60200190565b9061253882612516565b612545604051918261225a565b828152601f196125558294612516565b0190602036910137565b80518210156109f65760209160051b010190565b919082039182116109e057565b919080156127645760005b81811061271e575061259b6133ee565b6125b2336000526044602052604060002054151590565b80612702575b6126d457916125c68361252e565b600093849285915b808310612689575050506126407f6662f35bbdebb39ebf472c94252ecf0dd0c55fc8d0a922e0595b12c53d643937918561261761260f6126849798876122b5565b606154612573565b60615561262681603d546122b5565b603d55815190604051938493608085526080850190612183565b91602084015285604084015260608301520390a1336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b600190565b909193956126cb6001916126c56126a1888688612506565b916126b26020843594013584613c29565b9190936126bf8b8b61255f565b526122b5565b986122b5565b940191906125ce565b509050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106125b8565b602061272b828487612506565b01351561273a5760010161258b565b7f115e63aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f930363960000000000000000000000000000000000000000000000000000000060005260046000fd5b67ffffffffffffffff81116121f057601f01601f191660200190565b9291926127b68261278e565b916127c4604051938461225a565b8294818452818301116102dc578281602093846000960137010152565b60068210156107bc5752565b93929190916000918315612c5a5761287f916128706128769261286889604051612860816128528c60208301957f82186402b6a35725297ac7691ca0a6e5be47eef3d200a5bc491a4e89d99cadde876040919493926060820195825260208201520152565b03601f19810183528261225a565b519020613f07565b9236916127aa565b90614435565b90929192614471565b6001600160a01b0333911603612c32576128976133ee565b6128ae336000526044602052604060002054151590565b80612c18575b612bec576128c1846121b7565b604051936128ce8561223e565b81855260208501946128df826121b7565b81865251946006861015612bd857516128f7816121b7565b612900816121b7565b6040519561290d87612206565b8487526020870181905260408701918252906006821015612bc45751612932816121b7565b612948604051926129428461223e565b836127e1565b612951816121b7565b60208201526060549250855195600460405161296c81612222565b858152602081019889526001600160a01b03604082014281526060830190338252608084019b878d5289600052605f60205260406000209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff000000000000000000000000000000000000000082541617905501965196875160068110156107bc576000987fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612a32816121b7565b612a3b816121b7565b60081b16931691161717905580513388526046602052612a60604089209182546122b5565b90558051612a6d8361421e565b6018811015612bb05791612aac91612a8d612ad3946064019182546122b5565b905551923389526045602052612aa660408a209161421e565b906122d3565b612abf8294925492838360031b1c6122b5565b91906000199060031b92831b921b19161790565b9055612ade336148f1565b612b83575b612aef836061546122b5565b60615560605460018101809111611c2757612684949550606055612b12816121b7565b604051918252826020830152612b27816121b7565b60408201527f8629b9bbe2698bb9733aab9759f36ec9390475da11e15309b08e8755ea5862fd60603392a230336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b338552607e602052612b9a60026040872001613f48565b338552607e602052426001604087200155612ae3565b602489634e487b7160e01b81526032600452fd5b602484634e487b7160e01b81526021600452fd5b602483634e487b7160e01b81526021600452fd5b91925050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b08280a290565b50607d54338252607e6020526001604083200154106128b4565b807f710651b70000000000000000000000000000000000000000000000000000000060049252fd5b6004837f905091fe000000000000000000000000000000000000000000000000000000008152fd5b91908110156109f6576060020190565b9190801561302e5760005b818110612feb5750612cad6133ee565b612cc4336000526044602052604060002054151590565b80612fcf575b6126d457600090612cda8161252e565b93825b828110612d82575050612d42612684939482612d1c7f254fa5b38abff01b7e340b6d943b8e9c17ff5a3f7fe4a447dc3e92ffb3705a0e946060546122b5565b606055612d2b856061546122b5565b606155604051928392606084526060840190612183565b9060208301528460408301520390a130336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b612d8d818484612c82565b93612d9a853580926122b5565b946060813603126102dc5760405191612db283612206565b82526020820190602081013582526040808401910135815260009151906006821015612bd85751612de2816121b7565b612df2604051926129428461223e565b612dfb816121b7565b6020820152612e0c846060546122b5565b9280516004604051612e1d81612222565b868152602081019283526001600160a01b0360408201428152606083019033825260808401958887528a8a52605f60205260408a209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790550190518051906006821015612fbb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612ede816121b7565b612ee7816121b7565b60081b16931691161717905580513384526046602052612f0c604085209182546122b5565b90558051612f198361421e565b6018811015612fa75760019695949392612f5892612f3f612aac936064019182546122b5565b905551923385526045602052612aa6604086209161421e565b9055612f63336148f1565b612f7a575b50612f73828961255f565b5201612cdd565b338152607e602052612f9160026040832001613f48565b338152607e602052836040429220015538612f68565b602485634e487b7160e01b81526032600452fd5b602486634e487b7160e01b81526021600452fd5b50607d5433600052607e60205260016040600020015410612cca565b612ff6818386612c82565b351561300457600101612c9d565b7f905091fe0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f80ef666a0000000000000000000000000000000000000000000000000000000060005260046000fd5b91929092831561273a576130b991612870612876926128686040516020810190612860816128528c8c7fbda4078728c0ae5140c1cd85bf1abff4d86904b62dc8d2823ae88a05b34e2e37876040919493926060820195825260208201520152565b6001600160a01b03339116036131cb576130d16133ee565b6130e8336000526044602052604060002054151590565b806131af575b613182576130ff6126849282613c29565b909161310e61260f83856122b5565b60615561311d82603d546122b5565b603d556040519183835260208301527f277a1a847b4895ea9853b0f19969c04b8d17cb55b9ad4504179ede80fbf0f7af60403393a3336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b5050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106130ee565b7f710651b70000000000000000000000000000000000000000000000000000000060005260046000fd5b919082156133c45761324b916128706128769261286860405160208101907f66a29e1886f085dbd8ec4fc1f6b13da644ad0efad4477114339037860a802dfe82528860408201526040815261286060608261225a565b6001600160a01b03339116036131cb576132636133ee565b61327a336000526044602052604060002054151590565b806133a8575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206132b2828254612573565b9055603c549080821061332b576132cc8161229d93612573565b603c556040518181527fecf9667816c0bdb34ff25e5437430e488cebc3841c3a2c90e3927e07afcd78b460203392a2336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b7f4aa330410000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa689c4650000000000000000000000000000000000000000000000000000000060005260046000fd5b50337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2565b50607d5433600052607e60205260016040600020015410613280565b7f8f9ba5d50000000000000000000000000000000000000000000000000000000060005260046000fd5b6133f66138c5565b61340d336000526044602052604060002054151590565b1561229d5733600052607e602052600160406000200154607d54111561229d57336000526045602052613443604060002061227d565b60009060029060005b600481106134b95750506002146134a1575b33600052607e60205261347760406000209182546122b5565b9055337fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b33600052607e6020524260016040600020015561345e565b60005b600681106134cd575060010161344c565b60068202828104600614831517156109e057816134e9916122b5565b6134f381856122c2565b519081156135d45733600052607e6020526135158160026040600020016122d3565b90549060031b1c916102dc83018084116109e0576000838152607c602052604090205481106135ca575081600052607c602052604060002054925b905b8382106135855750509061357c6124616001949333600052607e60205260026040600020016122d3565b90555b016134bc565b90976135c2600191611bed6135ab846124a88e89600052607c60205260406000206122e3565b86600052607c6020526124c88d60406000206122e3565b980190613552565b6001975092613550565b505060019061357f565b33600052603f60205260406000206040516135f8816121d4565b815481526060600183015491602081019283526003600285015494604083019586520154918291015260009080421161375c575b5090816136cd575b50505033600052603f60205280604060002054106136c7576126849033600052603f6020526040600020613669828254612573565b90556040518181527fcf93002ecae3f252116b013a1ae8ec072919c19ab134261c065cc94d5b0b3e5860203392a2336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b50600090565b518082101561375557505b8062015180029162015180830482036109e0576137329233600052603f60205261370b60036040600020019182546122b5565b905533600052603f6020526001604060002001613729838254612573565b905551906121c1565b33600052603f60205261374b60406000209182546122b5565b9055388080613634565b90506136d8565b61376a620151809142612573565b04906001820180921161377e57503861362c565b80634e487b7160e01b602492526011600452fd5b80156133c4576137a06133ee565b6137b7336000526044602052604060002054151590565b80613866575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206137ef828254612573565b9055603c549080821061332b576138098161229d93612573565b603c557f916496170aac2ab5c6b8c47b015f9a422bab4f77114f70a76c7289a2e7312eb46020604051838152a1336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b50607d5433600052607e602052600160406000200154106137bd565b9060405161388f8161223e565b602060ff8294546138a2828216856127e1565b60081c16916138b0836121b7565b0152565b60ff1660ff81146109e05760010190565b60625460011461229d57606354421061229d576080541561229d576138e8613f96565b620151806138f860635442612573565b04600101806001116109e057600060189261391284612516565b90613920604051928361225a565b848252601f1961392f86612516565b0136602084013760405191606484845b888210613c12575050506139556103008461225a565b835b60ff8116946004861015613a365795926000906006870298878a0460061488151715985b60ff84166006811015613a19578a6109e057613997818d6122b5565b908a8a6139a484826122c2565b5115613a0b57926127106139f56126bf946139e0613a0598956139ca866139ff9a6122c2565b51906000526004602052604060002054906121c1565b906000526003602052604060002054906121c1565b049283918b61255f565b936138b4565b9261397b565b5050505092613a05906138b4565b50959750985092965050613a2c906138b4565b9490919394613957565b919592969450508015613be0576080549160005b60ff81166004811015613b8257801560068281029283041417159860005b60ff81166006811015613b6f578b6109e0578787613a91613a8a8e94886122b5565b809461255f565b518015613b625790613ac6613acb92613ac18b60405196613ab18861223e565b60008852600060208901526121c1565b6121c1565b6122ff565b9060208101918252613add838c6122c2565b51815282600052607c60205260406000208054680100000000000000008110156121f057613b10916001820181556122e3565b929092613b4c5760019151835551910155898110156109f6576047019081549160001983146109e0576001613b47930190556138b4565b613a68565b634e487b7160e01b600052600060045260246000fd5b50505050613b47906138b4565b5050985050613b7d906138b4565b613a4a565b505050505050505042607d55613b9d62015180420642612573565b6201518081018091116109e057607f54613bb6916122b5565b6063557f040bde66f3369b7fc094e7401b5de26a5be6900e0fb645651d59eb57b8620dbc600080a1565b5050505050613bf462015180420642612573565b6201518081018091116109e057607f54613c0d916122b5565b606355565b82548152600192830192919091019060200161393f565b919091613c3461416c565b80600052605f6020526040600020906001600160a01b0360038301541680156107d25784600184015410613e2e573303613e045760009380926004600282015491019060ff825460081c1690613c89826121b7565b816000526002602052604060002054620151808102908082046201518014901517156109e057613cb99042612573565b10613dc9575b503360005260466020526040600020613cd9838254612573565b9055613cec613ce782613882565b61421e565b60188110156109f657613d3b91613d2791606401613d0b858254612573565b9055336000526045602052612aa6613ce7604060002092613882565b8192915490612abf85838360031b1c612573565b905533600052604660205260406000205415613db7575b81600052605f602052613d6e6001604060002001918254612573565b905580600052605f60205260016040600020015415613d8c57509190565b600052605f602052600060046040822082815582600182015582600282015582600382015501559190565b613dc03361478c565b613d5257600080fd5b9195509250613dd7816121b7565b6000526005602052612710613df1604060002054866121c1565b0493613dfd8582612573565b9238613cbf565b7f387224d40000000000000000000000000000000000000000000000000000000060005260046000fd5b7fe0a6f7200000000000000000000000000000000000000000000000000000000060005260046000fd5b61229d926001600160a01b03604051937fa9059cbb000000000000000000000000000000000000000000000000000000006020860152166024840152604483015260448252613ea860648361225a565b61425f565b9091926001600160a01b0361229d9481604051957f23b872dd000000000000000000000000000000000000000000000000000000006020880152166024860152166044840152606483015260648252613ea860848361225a565b604290613f12614319565b90604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b60478114613f7f57604790604754906000915b60188310613f695750505050565b6001809194019283549481840155019192613f5b565b50565b6001600160a01b0360005416330361141957565b6031547f0000000000000000000000000000000000000000000000000000000000000001801562278d0080830292830414171592915b60325481146140d05760068110156109f6576003810284613ff1600983015442612573565b906109e0578310156140425760009060088101549150600701546000526003602052604060002055603154600181018091116109e05760069006603155600181018091116109e05760069006613fcc565b505091905b6033545b60345481146140ca5760048110156109f6576003810283614070601b83015442612573565b906109e0578510156140c357600090601a81015491506019015460005260046020526040600020556033546001810181116109e0576001600491086033556001810181116109e05760016004910861404b565b5050915050565b50915050565b509190614047565b801561414257604054106140e857565b6001600160a01b03600054163314158061412d575b61410357565b7f1c2a55280000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b03603b54163314156140fd565b7f11184a7b0000000000000000000000000000000000000000000000000000000060005260046000fd5b6035547f00000000000000000000000000000000000000000000000000000000000000019162278d0083029280840462278d001490151715915b60365481146140ca5760048110156109f65760038102836141cb602783015442612573565b906109e0578510156140c357600090602681015491506025015460005260056020526040600020556035546001810181116109e0576001600491086035556001810181116109e0576001600491086141a6565b60208101519061422d826121b7565b614236826121b7565b6006820291808304600614901517156109e0575160068110156107bc5761425c916122b5565b90565b6000806001600160a01b036142aa93169360208151910182865af13d15614311573d9061428b8261278e565b91614299604051938461225a565b82523d6000602084013e5b8361494b565b80519081151591826142ed575b50506142c05750565b7f5274afe70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b81925090602091810103126102dc57602001518015908115036102dc5738806142b7565b6060906142a4565b6001600160a01b037f000000000000000000000000cb3d2be5386a13c87944f9384214ead9a6dba7921630148061440c575b15614374577f1f1088bc52d56d74f7207acfdfab86e466cfd7b3211e8c1abcaaf8c3acad074c90565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f9dfb93b476d65e79d590230e42fa549a24db10aa55195af52886fa544a966ad560408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a0815261440660c08261225a565b51902090565b507f0000000000000000000000000000000000000000000000000000000000000001461461434b565b81519190604183036144665761445f92506020820151906060604084015193015160001a9061485c565b9192909190565b505060009160029190565b61447a816121b7565b80614483575050565b61448c816121b7565b600181036144be577ff645eedf0000000000000000000000000000000000000000000000000000000060005260046000fd5b6144c7816121b7565b600281036144fd57507ffce698f70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b600390614509816121b7565b146145115750565b7fd78bce0c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60ff81146145a25760ff811690601f8211614578576040805192614562828561225a565b60208452601f1960208501920136833783525290565b7fb3512b0c0000000000000000000000000000000000000000000000000000000060005260046000fd5b506040516000604154908160011c91600181168015614698575b60208410811461468457838552849291811561464757506001146145e7575b61425c9250038261225a565b506041600090815290917f7c9785e8241615bc80415d89775984a1337d15dc1bf4ce50f41988b2a2b336a75b81831061462b57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614613565b6020925061425c9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016145db565b602483634e487b7160e01b81526022600452fd5b92607f16926145bc565b60ff81146146c65760ff811690601f8211614578576040805192614562828561225a565b506040516000604254908160011c9160018116801561476a575b602084108114614684578385528492918115614647575060011461470a5761425c9250038261225a565b506042600090815290917f38dfe4635b27babeca8be38d3b448cb5161a639b899a14825ba9c8d7892eb8c35b81831061474e57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614736565b92607f16926146e0565b80548210156109f65760005260206000200190600090565b60008181526044602052604090205480156148555760001981018181116109e0576043549060001982019182116109e05781810361481b575b505050604354801561480557600019016147e0816043614774565b60001982549160031b1b19169055604355600052604460205260006040812055600190565b634e487b7160e01b600052603160045260246000fd5b61483d61482c612461936043614774565b90549060031b1c9283926043614774565b905560005260446020526040600020553880806147c5565b5050600090565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116148e5579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa156148d9576000516001600160a01b038116156148cd5790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b50505060009160039190565b806000526044602052604060002054156000146136c757604354680100000000000000008110156121f0576149326124618260018594016043556043614774565b9055604354906000526044602052604060002055600190565b9061498a575080511561496057805190602001fd5b7f1425ea420000000000000000000000000000000000000000000000000000000060005260046000fd5b815115806149d2575b61499b575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b50803b1561499356fea264697066735822122029bc36c2c231c119492b3ac9d5378cd3e143f3f82f63c8339cc7048bd177894764736f6c634300081a0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d812885994300000000000000000000000036ac695293d35bd90586b5353471c2004407ef6e00000000000000000000000041c31f690873ec5a642dd374e2f2dd1528d057300000000000000000000000000000000000000000000007b864634d3cc768000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6d34ae9275e1a900000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000036fdb8800968fb8b38d53ebce3c86f909c79637200000000000000000000000000000000000000000031ef6e017e6033e9a00000000000000000000000000000000000000000000000000000000000000000016800000000000000000000000000000000000000000000000000000000013c680000000000000000000000000000000000000000000006cf2f721f56aca1600000000000000000000000000000552002ec9504f3931019d63a8d992c98fcdf1eb70000000000000000000000000000000000000000003b41ea57328adb16b00000000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000000000000000000000000000000000000000ed4e0000000000000000000000000000000000000000000009a58bb5318a10ffd00000000000000000000000000000719d74be74980f94b2b9ee670f94f540661f9f6600000000000000000000000000000000000000000004a1de0780a6934f100000000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000076a700000000000000000000000000000000000000000000012870bb26f3ec17f000000000000000000000000000004c5e31774b09cf489c482a472bb78ae8c024c70100000000000000000000000000000000000000000003e41c59d913dabc00000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000bdbc41e0348b300000000000000000000000000000003ce0935e2f28339d6f9f1068356a90bf6d3173e3000000000000000000000000000000000000000000045348a87272a44a20000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000e2cb06bdfece5f6000000000000000000000000000009ff1299f5abea9ff1789c86e1c972ac1b3a8d38d0000000000000000000000000000000000000000000445f16bd0aef280c0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000004f1a0000000000000000000000000000000000000000000000f022435fc28028c00000000000000000000000000000ca4bde17f2a29fd041665cee2d206bc5c2ae507f00000000000000000000000000000000000000000004389a2f2eeb40b760000000000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000fd7980018631f2200000000000000000000000000000ae0a2dbdfdc0b9d1132b52796d5fcc9a19bd5dc300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004a1d89bb94865ec00000000000000000000000000000053423085f352a12f0b4c51e00c512e130ae02b3800000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000004a1d89bb94865ec000000000000000000000000000000af3bf534d2b026b9c72cb6fb30a5f7f879a6f4f1000000000000000000000000000000000000000000a7eaa0281559f34020000000000000000000000000000000000000000000000000000000000000000007080000000000000000000000000000000000000000000000000000000003b5380000000000000000000000000000000000000000000000004be4e7267b6ae00000000000000000000000000000344b16fe768582f69205897d240e2971b3ac0c09000000000000000000000000000000000000000000250ec5f358893873d0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000943b021e44ac2c4300000000000000000000000000000c5d2d690d683c6f7bb654e91fed293caec2a26b50000000000000000000000000000000000000000001851a42ee173aa2940000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000004a1e59e6490d2d9c0000000000000000000000000000018bf7f1cf41f840a7cb21618d5d1c1af7ca3e9080000000000000000000000000000000000000000002e524435ac3e59a8a0000000000000000000000000000000000000000000000000000000000000000004380000000000000000000000000000000000000000000000000000000001da9c00000000000000000000000000000000000000000000000031df9095a18f600000000000000000000000000000fdf5b0d1cd40223ef91027ce014cb4a31ac4ecf200000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000001bccb3fbc0c0014000000
-----Decoded View---------------
Arg [0] : _zndPlatform (address): 0xAe4F32dFa85B9e8c424A9604E7bF1D8128859943
Arg [1] : _discretionaryWithdrawalAccount (address): 0x36Ac695293D35BD90586B5353471c2004407eF6E
Arg [2] : _overLimitWithdrawalAccount (address): 0x41c31F690873Ec5A642Dd374e2F2Dd1528d05730
Arg [3] : _initialDailyReward (uint256): 36458000000000000000000
Arg [4] : _initialDiscretionaryPoolBalance (uint256): 0
Arg [5] : _initialRewardsPoolBalance (uint256): 52499520000000000000000000
Arg [6] : _vestingPayload (tuple[]):
Arg [1] : recipient (address): 0x36fDB8800968fB8b38d53ebce3c86f909c796372
Arg [2] : quantityToUnlockOverTime (uint256): 60368040000000000000000000
Arg [3] : numberOfDays (uint256): 360
Arg [4] : cliffPeriodEnd (uint256): 20736000
Arg [5] : initialUnlock (uint256): 8231960000000000000000000
Arg [1] : recipient (address): 0x552002EC9504f3931019d63A8D992C98fcDf1Eb7
Arg [2] : quantityToUnlockOverTime (uint256): 71637900000000000000000000
Arg [3] : numberOfDays (uint256): 300
Arg [4] : cliffPeriodEnd (uint256): 15552000
Arg [5] : initialUnlock (uint256): 11662100000000000000000000
Arg [1] : recipient (address): 0x719d74bE74980F94b2B9ee670f94f540661F9F66
Arg [2] : quantityToUnlockOverTime (uint256): 5600100000000000000000000
Arg [3] : numberOfDays (uint256): 300
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 1399900000000000000000000
Arg [1] : recipient (address): 0x4c5e31774b09cF489C482A472BB78Ae8C024c701
Arg [2] : quantityToUnlockOverTime (uint256): 4704000000000000000000000
Arg [3] : numberOfDays (uint256): 210
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 896000000000000000000000
Arg [1] : recipient (address): 0x3Ce0935E2f28339d6f9f1068356a90Bf6d3173E3
Arg [2] : quantityToUnlockOverTime (uint256): 5229000000000000000000000
Arg [3] : numberOfDays (uint256): 210
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 1071000000000000000000000
Arg [1] : recipient (address): 0x9Ff1299f5Abea9ff1789c86E1C972Ac1b3A8D38D
Arg [2] : quantityToUnlockOverTime (uint256): 5166000000000000000000000
Arg [3] : numberOfDays (uint256): 180
Arg [4] : cliffPeriodEnd (uint256): 5184000
Arg [5] : initialUnlock (uint256): 1134000000000000000000000
Arg [1] : recipient (address): 0xCa4BdE17f2a29fd041665cee2D206Bc5c2Ae507F
Arg [2] : quantityToUnlockOverTime (uint256): 5103000000000000000000000
Arg [3] : numberOfDays (uint256): 150
Arg [4] : cliffPeriodEnd (uint256): 2592000
Arg [5] : initialUnlock (uint256): 1197000000000000000000000
Arg [1] : recipient (address): 0xaE0A2DbDFDc0B9d1132B52796d5FCC9a19bd5dc3
Arg [2] : quantityToUnlockOverTime (uint256): 0
Arg [3] : numberOfDays (uint256): 0
Arg [4] : cliffPeriodEnd (uint256): 0
Arg [5] : initialUnlock (uint256): 5600000000000000000000000
Arg [1] : recipient (address): 0x53423085F352A12f0B4c51E00C512e130Ae02b38
Arg [2] : quantityToUnlockOverTime (uint256): 50400000000000000000000000
Arg [3] : numberOfDays (uint256): 720
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 5600000000000000000000000
Arg [1] : recipient (address): 0xAF3BF534D2b026b9c72Cb6fB30a5F7F879A6F4f1
Arg [2] : quantityToUnlockOverTime (uint256): 202998600000000000000000000
Arg [3] : numberOfDays (uint256): 1800
Arg [4] : cliffPeriodEnd (uint256): 62208000
Arg [5] : initialUnlock (uint256): 1400000000000000000000
Arg [1] : recipient (address): 0x344b16FE768582f69205897d240e2971b3AC0c09
Arg [2] : quantityToUnlockOverTime (uint256): 44800020000000000000000000
Arg [3] : numberOfDays (uint256): 180
Arg [4] : cliffPeriodEnd (uint256): 86400
Arg [5] : initialUnlock (uint256): 11199980000000000000000000
Arg [1] : recipient (address): 0xC5D2D690D683C6F7bB654e91Fed293cAec2A26B5
Arg [2] : quantityToUnlockOverTime (uint256): 29399760000000000000000000
Arg [3] : numberOfDays (uint256): 720
Arg [4] : cliffPeriodEnd (uint256): 86400
Arg [5] : initialUnlock (uint256): 5600240000000000000000000
Arg [1] : recipient (address): 0x18BF7f1CF41f840A7cB21618D5D1C1AF7CA3E908
Arg [2] : quantityToUnlockOverTime (uint256): 55999080000000000000000000
Arg [3] : numberOfDays (uint256): 1080
Arg [4] : cliffPeriodEnd (uint256): 31104000
Arg [5] : initialUnlock (uint256): 920000000000000000000
Arg [1] : recipient (address): 0xfDf5b0d1cd40223ef91027cE014Cb4A31AC4ECf2
Arg [2] : quantityToUnlockOverTime (uint256): 50400000000000000000000000
Arg [3] : numberOfDays (uint256): 1440
Arg [4] : cliffPeriodEnd (uint256): 86400
Arg [5] : initialUnlock (uint256): 2100480000000000000000000
-----Encoded View---------------
78 Constructor Arguments found :
Arg [0] : 000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943
Arg [1] : 00000000000000000000000036ac695293d35bd90586b5353471c2004407ef6e
Arg [2] : 00000000000000000000000041c31f690873ec5a642dd374e2f2dd1528d05730
Arg [3] : 0000000000000000000000000000000000000000000007b864634d3cc7680000
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [5] : 0000000000000000000000000000000000000000002b6d34ae9275e1a9000000
Arg [6] : 00000000000000000000000000000000000000000000000000000000000000e0
Arg [7] : 000000000000000000000000000000000000000000000000000000000000000e
Arg [8] : 00000000000000000000000036fdb8800968fb8b38d53ebce3c86f909c796372
Arg [9] : 00000000000000000000000000000000000000000031ef6e017e6033e9a00000
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000168
Arg [11] : 00000000000000000000000000000000000000000000000000000000013c6800
Arg [12] : 00000000000000000000000000000000000000000006cf2f721f56aca1600000
Arg [13] : 000000000000000000000000552002ec9504f3931019d63a8d992c98fcdf1eb7
Arg [14] : 0000000000000000000000000000000000000000003b41ea57328adb16b00000
Arg [15] : 000000000000000000000000000000000000000000000000000000000000012c
Arg [16] : 0000000000000000000000000000000000000000000000000000000000ed4e00
Arg [17] : 00000000000000000000000000000000000000000009a58bb5318a10ffd00000
Arg [18] : 000000000000000000000000719d74be74980f94b2b9ee670f94f540661f9f66
Arg [19] : 00000000000000000000000000000000000000000004a1de0780a6934f100000
Arg [20] : 000000000000000000000000000000000000000000000000000000000000012c
Arg [21] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [22] : 000000000000000000000000000000000000000000012870bb26f3ec17f00000
Arg [23] : 0000000000000000000000004c5e31774b09cf489c482a472bb78ae8c024c701
Arg [24] : 00000000000000000000000000000000000000000003e41c59d913dabc000000
Arg [25] : 00000000000000000000000000000000000000000000000000000000000000d2
Arg [26] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [27] : 00000000000000000000000000000000000000000000bdbc41e0348b30000000
Arg [28] : 0000000000000000000000003ce0935e2f28339d6f9f1068356a90bf6d3173e3
Arg [29] : 000000000000000000000000000000000000000000045348a87272a44a200000
Arg [30] : 00000000000000000000000000000000000000000000000000000000000000d2
Arg [31] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [32] : 00000000000000000000000000000000000000000000e2cb06bdfece5f600000
Arg [33] : 0000000000000000000000009ff1299f5abea9ff1789c86e1c972ac1b3a8d38d
Arg [34] : 0000000000000000000000000000000000000000000445f16bd0aef280c00000
Arg [35] : 00000000000000000000000000000000000000000000000000000000000000b4
Arg [36] : 00000000000000000000000000000000000000000000000000000000004f1a00
Arg [37] : 00000000000000000000000000000000000000000000f022435fc28028c00000
Arg [38] : 000000000000000000000000ca4bde17f2a29fd041665cee2d206bc5c2ae507f
Arg [39] : 00000000000000000000000000000000000000000004389a2f2eeb40b7600000
Arg [40] : 0000000000000000000000000000000000000000000000000000000000000096
Arg [41] : 0000000000000000000000000000000000000000000000000000000000278d00
Arg [42] : 00000000000000000000000000000000000000000000fd7980018631f2200000
Arg [43] : 000000000000000000000000ae0a2dbdfdc0b9d1132b52796d5fcc9a19bd5dc3
Arg [44] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [45] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [46] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [47] : 00000000000000000000000000000000000000000004a1d89bb94865ec000000
Arg [48] : 00000000000000000000000053423085f352a12f0b4c51e00c512e130ae02b38
Arg [49] : 00000000000000000000000000000000000000000029b09d79838b954c000000
Arg [50] : 00000000000000000000000000000000000000000000000000000000000002d0
Arg [51] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [52] : 00000000000000000000000000000000000000000004a1d89bb94865ec000000
Arg [53] : 000000000000000000000000af3bf534d2b026b9c72cb6fb30a5f7f879a6f4f1
Arg [54] : 000000000000000000000000000000000000000000a7eaa0281559f340200000
Arg [55] : 0000000000000000000000000000000000000000000000000000000000000708
Arg [56] : 0000000000000000000000000000000000000000000000000000000003b53800
Arg [57] : 00000000000000000000000000000000000000000000004be4e7267b6ae00000
Arg [58] : 000000000000000000000000344b16fe768582f69205897d240e2971b3ac0c09
Arg [59] : 000000000000000000000000000000000000000000250ec5f358893873d00000
Arg [60] : 00000000000000000000000000000000000000000000000000000000000000b4
Arg [61] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [62] : 0000000000000000000000000000000000000000000943b021e44ac2c4300000
Arg [63] : 000000000000000000000000c5d2d690d683c6f7bb654e91fed293caec2a26b5
Arg [64] : 0000000000000000000000000000000000000000001851a42ee173aa29400000
Arg [65] : 00000000000000000000000000000000000000000000000000000000000002d0
Arg [66] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [67] : 00000000000000000000000000000000000000000004a1e59e6490d2d9c00000
Arg [68] : 00000000000000000000000018bf7f1cf41f840a7cb21618d5d1c1af7ca3e908
Arg [69] : 0000000000000000000000000000000000000000002e524435ac3e59a8a00000
Arg [70] : 0000000000000000000000000000000000000000000000000000000000000438
Arg [71] : 0000000000000000000000000000000000000000000000000000000001da9c00
Arg [72] : 000000000000000000000000000000000000000000000031df9095a18f600000
Arg [73] : 000000000000000000000000fdf5b0d1cd40223ef91027ce014cb4a31ac4ecf2
Arg [74] : 00000000000000000000000000000000000000000029b09d79838b954c000000
Arg [75] : 00000000000000000000000000000000000000000000000000000000000005a0
Arg [76] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [77] : 00000000000000000000000000000000000000000001bccb3fbc0c0014000000
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|---|---|---|---|---|
| ETH | 100.00% | $0.031574 | 494,829,553.9235 | $15,623,505.87 |
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.