Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/StablecoinVault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { ERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {
SafeERC20Upgradeable,
IERC20Upgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { UtilLib } from "src/utils/UtilLib.sol";
/// @notice Interface for ERC20 tokens with metadata
interface IERC20Metadata {
/// @notice Returns the token's decimals
function decimals() external view returns (uint8);
}
/**
* @title Stablecoin Vault
* @author Kelp DAO
* @notice Upgradeable ERC‑4626 vault that pools user deposits and hands excess funds
* to an off‑chain strategy custodian wallet while issuing a floating‑price
* receipt token (vault shares).
*/
contract StablecoinVault is
Initializable,
ERC4626Upgradeable,
AccessControlUpgradeable,
PausableUpgradeable,
ReentrancyGuardUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice The maximum withdrawal delay allowed
uint256 public constant MAX_WITHDRAWAL_DELAY = 30 days;
/// @notice The maximum number of open (unclaimed) withdrawals allowed per user at any time
uint256 public constant MAX_WITHDRAWALS_PER_USER = 100;
/// @notice The maximum number of withdrawals that can be claimed in a single batch
uint256 public constant MAX_BATCH_CLAIM_WITHDRAWALS = 100;
/// @notice Pauser role constant
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @notice Operator role constant
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Custom errors
error InvalidDecimals();
error InvalidMinDeposit();
error InvalidWithdrawalDelay();
error InvalidMaxNumberOfWithdrawalsPerUser();
error UserNotWhitelisted();
error AlreadyWhitelisted();
error BelowMinDeposit();
error CannotWithdrawZeroShares();
error NothingQueued();
error NoAssetsToQueue();
error WithdrawalNotReady();
error NoExcessAssets();
error InsufficientShares();
error WithdrawalDoesNotExist();
error NotYourWithdrawal();
error WithdrawalAlreadyClaimed();
error WithdrawalLimitReached();
error InstantWithdrawalNotAllowed();
error NoWithdrawalsToClaim();
error TooManyWithdrawalsInBatch();
error InvalidMaxBatchClaimWithdrawals();
error SlippageExceeded();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when an address is added to the deposit whitelist
* @param account Address of the user added to the whitelist
*/
event AddedToWhitelist(address indexed account);
/**
* @notice Emitted when an address is removed from the deposit whitelist
* @param account Address of the user removed from the whitelist
*/
event RemovedFromWhitelist(address indexed account);
/**
* @notice Emitted when the minimum deposit amount is updated
* @param newMinDeposit The new minimum deposit amount
*/
event MinDepositUpdated(uint256 newMinDeposit);
/**
* @notice Emitted when the withdrawal delay is updated
* @param newWithdrawalDelay The new withdrawal delay in seconds
*/
event WithdrawalDelayUpdated(uint256 newWithdrawalDelay);
/**
* @notice Emitted when the maximum number of withdrawals per user is updated
* @param newMaxNumberOfWithdrawalsPerUser The new maximum number of withdrawals per user
*/
event MaxNumberOfWithdrawalsPerUserUpdated(uint256 newMaxNumberOfWithdrawalsPerUser);
/**
* @notice Emitted when the maximum number of withdrawals that can be claimed in a batch is updated
* @param newMaxBatchClaimWithdrawals The new maximum number of withdrawals that can be claimed in a batch
*/
event MaxBatchClaimWithdrawalsUpdated(uint256 newMaxBatchClaimWithdrawals);
/**
* @notice Emitted when the custodian deposit address is updated
* @param newCustodianDepositAddress The new custodian deposit address
*/
event CustodianDepositAddressUpdated(address indexed newCustodianDepositAddress);
/**
* @notice Emitted when a withdrawal is queued
* @param withdrawalId Unique ID of the queued withdrawal
* @param user Address of the user who queued the withdrawal
* @param assets Amount of assets queued for withdrawal
* @param sharesToRedeem Amount of shares to be redeemed when the withdrawal is claimed
* @param unlockTime Timestamp when the withdrawal can be claimed
*/
event WithdrawalQueued(
uint256 withdrawalId, address indexed user, uint256 assets, uint256 sharesToRedeem, uint256 unlockTime
);
/**
* @notice Emitted when a withdrawal is completed
* @param withdrawalId Unique ID of the completed withdrawal
* @param user Address of the user who completed the withdrawal
* @param assets Amount of assets withdrawn
* @param sharesBurned Amount of shares burned during the withdrawal
*/
event WithdrawalCompleted(uint256 withdrawalId, address indexed user, uint256 assets, uint256 sharesBurned);
/**
* @notice Emitted when multiple withdrawals are claimed in a batch
* @param count Number of withdrawals claimed in the batch
* @param sender Address of the operator who performed the batch claim
*/
event BatchClaimCompleted(uint256 count, address indexed sender);
/**
* @notice Emitted when assets are swept to the custodian wallet
* @param custodianDepositAddress Address of the custodian wallet where assets are swept
* @param amount Amount of assets swept to the custodian wallet
*/
event AssetsSweptToCustodian(address indexed custodianDepositAddress, uint256 amount);
/**
* @notice Emitted when the latest custodian balance is updated
* @param newBalance The new custodian balance
* @param timestamp The timestamp when the balance was recorded
* @param reporter The address of the operator who reported the new balance
*/
event CustodianBalanceUpdated(uint256 newBalance, uint256 timestamp, address indexed reporter);
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Represents a queued withdrawal request
* @param withdrawalId Unique ID of the withdrawal request
* @param user Address of the user who queued the withdrawal
* @param assets Amount of assets owed to the user
* @param shares Shares to be burned once the withdrawal is claimable
* @param unlockTime When the withdrawal can be claimed (timestamp)
* @param claimed Whether the withdrawal has been claimed or not
*/
struct WithdrawalRequest {
uint256 withdrawalId;
address user;
uint256 assets;
uint256 shares;
uint256 unlockTime;
bool claimed;
}
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
/// @notice Whitelisted addresses allowed to deposit into the vault
mapping(address user => bool allowed) public whitelist;
/// @notice Minimal deposit denominated in the underlying asset’s decimals
uint256 public minDeposit;
/// @notice Minimum delay in seconds before a withdrawal can be claimed after queuing
uint256 public withdrawalDelay;
/// @notice The maximum number of withdrawals that any user can have open (unclaimed) at any time
uint256 public maxNumberOfWithdrawalsPerUser;
/// @notice The maximum number of withdrawals that can be claimed in a single batch
uint256 public maxBatchClaimWithdrawals;
/// @notice Total assets currently queued for withdrawal
uint256 public totalAssetsQueuedForWithdrawal;
/// @notice A global incremental counter for withdrawal IDs
uint256 public withdrawalCounter;
/// @notice Mapping of withdrawal IDs to withdrawal requests
mapping(uint256 withdrawalId => WithdrawalRequest withdrawalRequest) public withdrawals;
/// @notice Mapping of user addresses to their withdrawal IDs
mapping(address user => uint256[] withdrawalIds) public userWithdrawalIds;
/// @notice Address of the custodian's wallet where excess assets are swept
address public custodianDepositAddress;
/// @notice The latest custodian balance
uint256 public latestCustodianBalance;
/// @notice The timestamp at which the latest custodian balance was recorded
uint256 public latestCustodianBalanceUpdate;
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/**
* @notice Modifier to check if the user is whitelisted for deposits or not
* @param user The address of the user to check
*/
modifier onlyWhitelistedUser(address user) {
if (!whitelist[user]) revert UserNotWhitelisted();
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/*//////////////////////////////////////////////////////////////
INITIALIZER
//////////////////////////////////////////////////////////////*/
/**
* @param asset_ The underlying ERC‑20 token of the vault (e.g. USDT)
* @param name_ Vault share name
* @param symbol_ Vault share symbol
* @param admin Address of the admin who can change the vault parameters
* @param operator Address of the operator who can perform certain operational actions (e.g. sweep excess assets to
* custodian, update reported custodian balance or batch claim withdrawals on behalf of users)
* @param custodianDepositAddress_ Destination wallet for strategy funds
* @param minDeposit_ Initial minimum deposit amount in underlying asset's decimals
* @param withdrawalDelay_ Initial withdrawal delay in seconds (e.g. 7 days)
* @param maxNumberOfWithdrawalsPerUser_ Initial maximum number of withdrawals per user (e.g. 5)
* @param maxBatchClaimWithdrawals_ Initial maximum number of withdrawals that can be claimed in a batch
*/
function initialize(
IERC20Upgradeable asset_,
string memory name_,
string memory symbol_,
address admin,
address operator,
address custodianDepositAddress_,
uint256 minDeposit_,
uint256 withdrawalDelay_,
uint256 maxNumberOfWithdrawalsPerUser_,
uint256 maxBatchClaimWithdrawals_
)
external
initializer
{
UtilLib.checkNonZeroAddress(address(asset_));
UtilLib.checkNonZeroAddress(admin);
UtilLib.checkNonZeroAddress(operator);
UtilLib.checkNonZeroAddress(custodianDepositAddress_);
if (IERC20Metadata(address(asset_)).decimals() > 18) {
revert InvalidDecimals();
}
__ERC4626_init(asset_);
__ERC20_init(name_, symbol_);
__AccessControl_init();
__Pausable_init();
__ReentrancyGuard_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
_grantRole(OPERATOR_ROLE, operator);
if (minDeposit_ == 0) revert InvalidMinDeposit();
if (withdrawalDelay_ == 0 || withdrawalDelay_ > MAX_WITHDRAWAL_DELAY) {
revert InvalidWithdrawalDelay();
}
if (maxNumberOfWithdrawalsPerUser_ == 0 || maxNumberOfWithdrawalsPerUser_ > MAX_WITHDRAWALS_PER_USER) {
revert InvalidMaxNumberOfWithdrawalsPerUser();
}
if (maxBatchClaimWithdrawals_ == 0 || maxBatchClaimWithdrawals_ > MAX_BATCH_CLAIM_WITHDRAWALS) {
revert InvalidMaxBatchClaimWithdrawals();
}
custodianDepositAddress = custodianDepositAddress_;
minDeposit = minDeposit_;
withdrawalDelay = withdrawalDelay_;
maxNumberOfWithdrawalsPerUser = maxNumberOfWithdrawalsPerUser_;
maxBatchClaimWithdrawals = maxBatchClaimWithdrawals_;
latestCustodianBalance = 0;
latestCustodianBalanceUpdate = 0;
emit CustodianDepositAddressUpdated(custodianDepositAddress_);
emit MinDepositUpdated(minDeposit_);
emit WithdrawalDelayUpdated(withdrawalDelay_);
emit MaxNumberOfWithdrawalsPerUserUpdated(maxNumberOfWithdrawalsPerUser_);
emit MaxBatchClaimWithdrawalsUpdated(maxBatchClaimWithdrawals_);
}
/*//////////////////////////////////////////////////////////////
ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Pauses the contract
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
/// @notice Unpauses the contract
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
/**
* @notice Adds `account` to the deposit whitelist.
* @param account Address to add to the whitelist.
*/
function addToWhitelist(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
UtilLib.checkNonZeroAddress(account);
if (whitelist[account]) revert AlreadyWhitelisted();
whitelist[account] = true;
emit AddedToWhitelist(account);
}
/**
* @notice Removes `account` from the deposit whitelist.
* @param account Address to remove from the whitelist.
*/
function removeFromWhitelist(address account) external onlyRole(DEFAULT_ADMIN_ROLE) onlyWhitelistedUser(account) {
UtilLib.checkNonZeroAddress(account);
whitelist[account] = false;
emit RemovedFromWhitelist(account);
}
/**
* @notice Sets a new minimum deposit amount.
* @param newMinDeposit New minimum deposit amount in the underlying asset's decimals.
*/
function setMinDeposit(uint256 newMinDeposit) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (newMinDeposit == 0) revert InvalidMinDeposit();
minDeposit = newMinDeposit;
emit MinDepositUpdated(newMinDeposit);
}
/**
* @notice Sets a new withdrawal delay.
* @param newWithdrawalDelay New withdrawal delay in seconds.
*/
function setWithdrawalDelay(uint256 newWithdrawalDelay) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (newWithdrawalDelay == 0 || newWithdrawalDelay > MAX_WITHDRAWAL_DELAY) {
revert InvalidWithdrawalDelay();
}
withdrawalDelay = newWithdrawalDelay;
emit WithdrawalDelayUpdated(newWithdrawalDelay);
}
/**
* @notice Updates the maximum number of withdrawals per user
* @param _maxNumberOfWithdrawalsPerUser The new maximum number of withdrawals per user
*/
function setMaxNumberOfWithdrawalsPerUser(uint256 _maxNumberOfWithdrawalsPerUser)
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
if (_maxNumberOfWithdrawalsPerUser == 0 || _maxNumberOfWithdrawalsPerUser > MAX_WITHDRAWALS_PER_USER) {
revert InvalidMaxNumberOfWithdrawalsPerUser();
}
maxNumberOfWithdrawalsPerUser = _maxNumberOfWithdrawalsPerUser;
emit MaxNumberOfWithdrawalsPerUserUpdated(_maxNumberOfWithdrawalsPerUser);
}
/**
* @notice Updates the maximum number of withdrawals that can be claimed in a batch
* @param _maxBatchClaimWithdrawals The new maximum number of withdrawals that can be claimed in a batch
*/
function setMaxBatchClaimWithdrawals(uint256 _maxBatchClaimWithdrawals) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_maxBatchClaimWithdrawals == 0 || _maxBatchClaimWithdrawals > MAX_BATCH_CLAIM_WITHDRAWALS) {
revert InvalidMaxBatchClaimWithdrawals();
}
maxBatchClaimWithdrawals = _maxBatchClaimWithdrawals;
emit MaxBatchClaimWithdrawalsUpdated(_maxBatchClaimWithdrawals);
}
/**
* @notice Sets a new custodian deposit address.
* @param newCustodianDepositAddress New custodian deposit address.
*/
function setCustodianDepositAddress(address newCustodianDepositAddress) external onlyRole(DEFAULT_ADMIN_ROLE) {
UtilLib.checkNonZeroAddress(newCustodianDepositAddress);
custodianDepositAddress = newCustodianDepositAddress;
emit CustodianDepositAddressUpdated(newCustodianDepositAddress);
}
/*//////////////////////////////////////////////////////////////
OPERATOR FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Transfers the idle assets to the custodian deposit address
function sweepToCustodian() external nonReentrant onlyRole(OPERATOR_ROLE) {
IERC20Upgradeable assetToken = IERC20Upgradeable(asset());
uint256 excessAssets = getExcessAssets();
if (excessAssets == 0) revert NoExcessAssets();
assetToken.safeTransfer(custodianDepositAddress, excessAssets);
// After sweeping assets to the custodian, we assume that the custodian balance has increased by the same amount
latestCustodianBalance += excessAssets;
emit AssetsSweptToCustodian(custodianDepositAddress, excessAssets);
}
/**
* @notice Updates the latest custodian balance and timestamp
* @param newBalance The new custodian balance
*/
function updateCustodianBalance(uint256 newBalance) external nonReentrant onlyRole(OPERATOR_ROLE) {
latestCustodianBalance = newBalance;
latestCustodianBalanceUpdate = block.timestamp;
emit CustodianBalanceUpdated(newBalance, block.timestamp, msg.sender);
}
/*//////////////////////////////////////////////////////////////
ERC4626 OVERRIDES
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc ERC4626Upgradeable
* @dev Use same decimals as the underlying asset in order to avoid any possible precision loss when
* converting between assets and shares.
*/
function decimals() public view override returns (uint8) {
return IERC20Metadata(asset()).decimals();
}
/**
* @inheritdoc ERC4626Upgradeable
* @notice Overrides the totalAssets function from ERC4626 to include assets reported by the custodian
*/
function totalAssets() public view override returns (uint256) {
IERC20Upgradeable assetToken = IERC20Upgradeable(asset());
return assetToken.balanceOf(address(this)) + latestCustodianBalance;
}
/**
* @inheritdoc ERC4626Upgradeable
* @dev Overrides the ERC4626Upgradeable deposit function to enforce whitelist and minimum deposit checks.
*/
function _deposit(
address caller,
address receiver,
uint256 assets,
uint256 shares
)
internal
override
nonReentrant
whenNotPaused
onlyWhitelistedUser(caller)
onlyWhitelistedUser(receiver)
{
if (assets < minDeposit) revert BelowMinDeposit();
super._deposit(caller, receiver, assets, shares);
}
/**
* @inheritdoc ERC4626Upgradeable
* @dev Overrides the ERC4626Upgradeable withdraw function to disallow instant withdrawals.
*/
function withdraw(uint256, address, address) public pure override returns (uint256) {
revert InstantWithdrawalNotAllowed();
}
/**
* @inheritdoc ERC4626Upgradeable
* @dev Overrides the ERC4626Upgradeable redeem function to disallow instant redemptions.
*/
function redeem(uint256, address, address) public pure override returns (uint256) {
revert InstantWithdrawalNotAllowed();
}
/**
* @inheritdoc ERC4626Upgradeable
* @dev Overrides the ERC4626Upgradeable maxWithdraw function to return 0, to signal that instant withdrawals
* are not allowed.
*/
function maxWithdraw(address) public pure override returns (uint256) {
return 0;
}
/**
* @inheritdoc ERC4626Upgradeable
* @dev Overrides the ERC4626Upgradeable maxRedeem function to return 0, to signal that instant redemptions
* are not allowed.
*/
function maxRedeem(address) public pure override returns (uint256) {
return 0;
}
/*//////////////////////////////////////////////////////////////
DEPOSITS
//////////////////////////////////////////////////////////////*/
/**
* @notice Deposits `assets` of underlying tokens and mints vault shares to `receiver`.
* @dev A modified version of the ERC‑4626 `deposit` function that includes a slippage check.
* @param assets Amount of underlying tokens to deposit.
* @param receiver Address to receive the minted vault shares.
* @param minSharesOut Minimum amount of shares that the user expects to receive.
* @return shares Amount of vault shares minted to the `receiver`.
*/
function deposit(uint256 assets, address receiver, uint256 minSharesOut) external returns (uint256 shares) {
shares = previewDeposit(assets);
if (shares < minSharesOut) revert SlippageExceeded();
_deposit(msg.sender, receiver, assets, shares);
}
/*//////////////////////////////////////////////////////////////
WITHDRAWALS
//////////////////////////////////////////////////////////////*/
/**
* @notice Queues a withdrawal request that becomes claimable after `withdrawalDelay`.
* @param shares Amount of vault shares to redeem.
* @param minAssetsOut Minimum amount of assets that the user expects to receive when claiming the withdrawal.
* @return assets Amount of assets to be withdrawn, based on the current exchange rate.
*/
function queueWithdrawal(
uint256 shares,
uint256 minAssetsOut
)
external
nonReentrant
whenNotPaused
onlyWhitelistedUser(msg.sender)
returns (uint256 assets)
{
IERC20Upgradeable shareToken = IERC20Upgradeable(address(this));
if (shares == 0) revert CannotWithdrawZeroShares();
if (shares > shareToken.balanceOf(msg.sender)) revert InsufficientShares();
if (userWithdrawalIds[msg.sender].length >= maxNumberOfWithdrawalsPerUser) revert WithdrawalLimitReached();
// Snapshots the minimum amount of assets that a user will receive when they claim the withdrawal and
// adds it to the total assets queued for withdrawal
assets = previewRedeem(shares);
if (assets == 0) revert NoAssetsToQueue();
if (assets < minAssetsOut) revert SlippageExceeded();
totalAssetsQueuedForWithdrawal += assets;
// Transfer vault shares to the vault itself to lock them for the withdrawal
shareToken.safeTransferFrom(msg.sender, address(this), shares);
// Create a new withdrawal request
uint256 withdrawalId = ++withdrawalCounter;
uint256 unlockTime = block.timestamp + withdrawalDelay;
withdrawals[withdrawalId] = WithdrawalRequest({
withdrawalId: withdrawalId,
user: msg.sender,
assets: assets,
shares: shares,
unlockTime: unlockTime,
claimed: false
});
userWithdrawalIds[msg.sender].push(withdrawalId);
emit WithdrawalQueued(withdrawalId, msg.sender, assets, shares, unlockTime);
}
/**
* @notice Completes a previously queued withdrawal request.
* @param withdrawalId The ID of the withdrawal request to claim.
* @return assets The amount of assets withdrawn, based on the current exchange rate.
*/
function claimWithdrawal(uint256 withdrawalId) external nonReentrant returns (uint256 assets) {
WithdrawalRequest storage req = withdrawals[withdrawalId];
if (req.user == address(0)) revert WithdrawalDoesNotExist();
if (req.user != msg.sender) revert NotYourWithdrawal();
if (req.assets == 0) revert NothingQueued();
if (block.timestamp < req.unlockTime) revert WithdrawalNotReady();
if (req.claimed) revert WithdrawalAlreadyClaimed();
// Finalize the claim
assets = _finalizeClaim(withdrawalId, req, msg.sender);
}
/**
* @notice Batch claims multiple queued withdrawals.
* @param withdrawalIds Array of withdrawal IDs to claim.
*/
function batchClaimWithdrawals(uint256[] calldata withdrawalIds)
external
nonReentrant
onlyRole(OPERATOR_ROLE)
returns (uint256[] memory assets)
{
uint256 length = withdrawalIds.length;
if (length == 0) revert NoWithdrawalsToClaim();
if (length > maxBatchClaimWithdrawals) revert TooManyWithdrawalsInBatch();
assets = new uint256[](length);
uint256 processedCount = 0;
for (uint256 i = 0; i < length; ++i) {
uint256 withdrawalId = withdrawalIds[i];
WithdrawalRequest storage req = withdrawals[withdrawalId];
if (!_isClaimable(req)) continue; // skip unclaimable requests
assets[i] = _finalizeClaim(withdrawalId, req, req.user);
++processedCount;
}
if (processedCount > 0) emit BatchClaimCompleted(processedCount, msg.sender);
}
/*/////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Returns the current exchange rate of 1 vault share to the underlying asset.
function exchangeRate() external view returns (uint256) {
return convertToAssets(10 ** decimals());
}
/**
* @notice Returns the amount of excess assets currently present in the vault
* @dev Excess assets = vault's current balance of an underlying asset ‑ totalAssetsQueuedForWithdrawal
*/
function getExcessAssets() public view returns (uint256) {
uint256 assetsInVault = IERC20Upgradeable(asset()).balanceOf(address(this));
if (assetsInVault <= totalAssetsQueuedForWithdrawal) {
return 0;
}
return assetsInVault - totalAssetsQueuedForWithdrawal;
}
/**
* @notice Returns the withdrawal IDs for a given user
* @param _user The user for which to retrieve withdrawal IDs
* @return The withdrawal IDs
*/
function getUserWithdrawalIds(address _user) external view returns (uint256[] memory) {
return userWithdrawalIds[_user];
}
/**
* @notice Returns the withdrawal request details for a given withdrawal ID
* @param _withdrawalId The ID of the withdrawal to retrieve
* @return The withdrawal request details
*/
function getWithdrawal(uint256 _withdrawalId) external view returns (WithdrawalRequest memory) {
return withdrawals[_withdrawalId];
}
/**
* @notice Returns all of the currently unclaimed withdrawals for a given user
* @param _user The user for which to retrieve withdrawals
* @return The withdrawals
*/
function getUserWithdrawals(address _user) public view returns (WithdrawalRequest[] memory) {
uint256[] memory withdrawalIds = userWithdrawalIds[_user];
uint256 length = withdrawalIds.length;
WithdrawalRequest[] memory userWithdrawals = new WithdrawalRequest[](length);
if (length == 0) {
return userWithdrawals;
}
for (uint256 i = 0; i < length; ++i) {
userWithdrawals[i] = withdrawals[withdrawalIds[i]];
}
return userWithdrawals;
}
/**
* @notice Returns all of the currently pending (non-claimable) withdrawals for a given use
* @param _user The user for which to retrieve withdrawals
* @return The withdrawals
*/
function getPendingUserWithdrawals(address _user) external view returns (WithdrawalRequest[] memory) {
WithdrawalRequest[] memory userWithdrawals = getUserWithdrawals(_user);
uint256 length = userWithdrawals.length;
if (length == 0) {
return userWithdrawals;
}
uint256 pendingCount = 0;
for (uint256 i = 0; i < length; ++i) {
if (block.timestamp < userWithdrawals[i].unlockTime) {
++pendingCount;
}
}
WithdrawalRequest[] memory pendingWithdrawals = new WithdrawalRequest[](pendingCount);
uint256 pendingIndex = 0;
for (uint256 i = 0; i < length; ++i) {
if (block.timestamp < userWithdrawals[i].unlockTime) {
pendingWithdrawals[pendingIndex] = userWithdrawals[i];
++pendingIndex;
}
}
return pendingWithdrawals;
}
/**
* @notice Returns all of the currently claimable withdrawals for a given user
* @param _user The user for which to retrieve withdrawals
* @return The withdrawals
*/
function getClaimableUserWithdrawals(address _user) external view returns (WithdrawalRequest[] memory) {
WithdrawalRequest[] memory userWithdrawals = getUserWithdrawals(_user);
uint256 length = userWithdrawals.length;
if (length == 0) {
return userWithdrawals;
}
uint256 claimableCount = 0;
for (uint256 i = 0; i < length; ++i) {
if (block.timestamp >= userWithdrawals[i].unlockTime) {
++claimableCount;
}
}
WithdrawalRequest[] memory claimableWithdrawals = new WithdrawalRequest[](claimableCount);
uint256 claimableIndex = 0;
for (uint256 i = 0; i < length; ++i) {
if (block.timestamp >= userWithdrawals[i].unlockTime) {
claimableWithdrawals[claimableIndex] = userWithdrawals[i];
++claimableIndex;
}
}
return claimableWithdrawals;
}
/**
* @notice Returns whether a withdrawal is ready to be claimed or not
* @param _withdrawalId The ID of the withdrawal to check
* @return Whether the withdrawal is ready to be claimed or not
*/
function isWithdrawalClaimable(uint256 _withdrawalId) external view returns (bool) {
return _isClaimable(withdrawals[_withdrawalId]);
}
/**
* @notice Returns whether a withdrawal has been claimed or not
* @param _withdrawalId The ID of the withdrawal to check
* @return Whether the withdrawal has been claimed or not
*/
function isWithdrawalClaimed(uint256 _withdrawalId) external view returns (bool) {
return withdrawals[_withdrawalId].claimed;
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Checks if a withdrawal request is claimable.
* @param req The withdrawal request to check.
* @return Whether the withdrawal request is claimable or not.
*/
function _isClaimable(WithdrawalRequest storage req) internal view returns (bool) {
return (req.user != address(0) && req.assets != 0 && block.timestamp >= req.unlockTime && !req.claimed);
}
/**
* @notice Finalizes the claim of a withdrawal request.
* @param withdrawalId The ID of the withdrawal request to finalize.
* @param req The withdrawal request to finalize.
* @param receiver The address that will receive the withdrawn assets.
* @return assets The amount of assets withdrawn, based on the current exchange rate.
*/
function _finalizeClaim(
uint256 withdrawalId,
WithdrawalRequest storage req,
address receiver
)
internal
returns (uint256 assets)
{
// Mark the withdrawal as claimed
req.claimed = true;
// Remove the withdrawal ID from the user's list of withdrawal IDs (swap & pop)
uint256[] storage ids = userWithdrawalIds[req.user];
uint256 length = ids.length;
for (uint256 i = 0; i < length; ++i) {
if (ids[i] == withdrawalId) {
ids[i] = ids[length - 1];
ids.pop();
break;
}
}
// Remove the withdrawn assets from the total assets queued for withdrawal
assets = req.assets;
totalAssetsQueuedForWithdrawal -= assets;
// Burn the shares from the vault
_burn(address(this), req.shares);
// Transfer the withdrawable amount of assets to the user
IERC20Upgradeable assetToken = IERC20Upgradeable(asset());
assetToken.safeTransfer(receiver, assets);
emit WithdrawalCompleted(withdrawalId, req.user, assets, req.shares);
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
StringsUpgradeable.toHexString(account),
" is missing role ",
StringsUpgradeable.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol)
pragma solidity ^0.8.0;
import "../ERC20Upgradeable.sol";
import "../utils/SafeERC20Upgradeable.sol";
import "../../../interfaces/IERC4626Upgradeable.sol";
import "../../../utils/math/MathUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in
* https://eips.ethereum.org/EIPS/eip-4626[EIP-4626].
*
* This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for
* underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
* the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this
* contract and not the "assets" token which is an independent contract.
*
* [CAUTION]
* ====
* In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
* with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
* attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
* deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
* similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
* verifying the amount received is as expected, using a wrapper that performs these checks such as
* https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
*
* Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()`
* corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault
* decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself
* determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset
* (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's
* donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more
* expensive than it is profitable. More details about the underlying math can be found
* xref:erc4626.adoc#inflation-attack[here].
*
* The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
* to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
* will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
* bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
* `_convertToShares` and `_convertToAssets` functions.
*
* To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
* ====
*
* _Available since v4.7._
*/
abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626Upgradeable {
using MathUpgradeable for uint256;
IERC20Upgradeable private _asset;
uint8 private _underlyingDecimals;
/**
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
*/
function __ERC4626_init(IERC20Upgradeable asset_) internal onlyInitializing {
__ERC4626_init_unchained(asset_);
}
function __ERC4626_init_unchained(IERC20Upgradeable asset_) internal onlyInitializing {
(bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
_underlyingDecimals = success ? assetDecimals : 18;
_asset = asset_;
}
/**
* @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
*/
function _tryGetAssetDecimals(IERC20Upgradeable asset_) private view returns (bool, uint8) {
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
abi.encodeWithSelector(IERC20MetadataUpgradeable.decimals.selector)
);
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
return (true, uint8(returnedDecimals));
}
}
return (false, 0);
}
/**
* @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
* "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
* asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
*
* See {IERC20Metadata-decimals}.
*/
function decimals() public view virtual override(IERC20MetadataUpgradeable, ERC20Upgradeable) returns (uint8) {
return _underlyingDecimals + _decimalsOffset();
}
/** @dev See {IERC4626-asset}. */
function asset() public view virtual override returns (address) {
return address(_asset);
}
/** @dev See {IERC4626-totalAssets}. */
function totalAssets() public view virtual override returns (uint256) {
return _asset.balanceOf(address(this));
}
/** @dev See {IERC4626-convertToShares}. */
function convertToShares(uint256 assets) public view virtual override returns (uint256) {
return _convertToShares(assets, MathUpgradeable.Rounding.Down);
}
/** @dev See {IERC4626-convertToAssets}. */
function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
return _convertToAssets(shares, MathUpgradeable.Rounding.Down);
}
/** @dev See {IERC4626-maxDeposit}. */
function maxDeposit(address) public view virtual override returns (uint256) {
return type(uint256).max;
}
/** @dev See {IERC4626-maxMint}. */
function maxMint(address) public view virtual override returns (uint256) {
return type(uint256).max;
}
/** @dev See {IERC4626-maxWithdraw}. */
function maxWithdraw(address owner) public view virtual override returns (uint256) {
return _convertToAssets(balanceOf(owner), MathUpgradeable.Rounding.Down);
}
/** @dev See {IERC4626-maxRedeem}. */
function maxRedeem(address owner) public view virtual override returns (uint256) {
return balanceOf(owner);
}
/** @dev See {IERC4626-previewDeposit}. */
function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
return _convertToShares(assets, MathUpgradeable.Rounding.Down);
}
/** @dev See {IERC4626-previewMint}. */
function previewMint(uint256 shares) public view virtual override returns (uint256) {
return _convertToAssets(shares, MathUpgradeable.Rounding.Up);
}
/** @dev See {IERC4626-previewWithdraw}. */
function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
return _convertToShares(assets, MathUpgradeable.Rounding.Up);
}
/** @dev See {IERC4626-previewRedeem}. */
function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
return _convertToAssets(shares, MathUpgradeable.Rounding.Down);
}
/** @dev See {IERC4626-deposit}. */
function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
uint256 shares = previewDeposit(assets);
_deposit(_msgSender(), receiver, assets, shares);
return shares;
}
/** @dev See {IERC4626-mint}.
*
* As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero.
* In this case, the shares will be minted without requiring any assets to be deposited.
*/
function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
require(shares <= maxMint(receiver), "ERC4626: mint more than max");
uint256 assets = previewMint(shares);
_deposit(_msgSender(), receiver, assets, shares);
return assets;
}
/** @dev See {IERC4626-withdraw}. */
function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) {
require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");
uint256 shares = previewWithdraw(assets);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return shares;
}
/** @dev See {IERC4626-redeem}. */
function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) {
require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
uint256 assets = previewRedeem(shares);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return assets;
}
/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*/
function _convertToShares(uint256 assets, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) {
return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
}
/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function _convertToAssets(uint256 shares, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
}
/**
* @dev Deposit/mint common workflow.
*/
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
// If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
// `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
// calls the vault, which is assumed not malicious.
//
// Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
// assets are transferred and before the shares are minted, which is a valid state.
// slither-disable-next-line reentrancy-no-eth
SafeERC20Upgradeable.safeTransferFrom(_asset, caller, address(this), assets);
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}
/**
* @dev Withdraw/redeem common workflow.
*/
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
// `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
// calls the vault, which is assumed not malicious.
//
// Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
// shares are burned and after the assets are transferred, which is a valid state.
_burn(owner, shares);
SafeERC20Upgradeable.safeTransfer(_asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
function _decimalsOffset() internal view virtual returns (uint8) {
return 0;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
Submitted on: 2025-09-17 15:03:24
Comments
Log in to comment.
No comments yet.