Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/vaults/BracketVaultV2.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {IBracketVaultV2} from "./IBracketVaultV2.sol";
import {RebasingToken, IRebasingToken} from "./RebasingToken.sol";
import {AccessControlUpgradeable} from "openzeppelin-contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {PausableUpgradeable} from "openzeppelin-contracts-upgradeable/utils/PausableUpgradeable.sol";
import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
contract BracketVaultV2 is RebasingToken, AccessControlUpgradeable, PausableUpgradeable, IBracketVaultV2 {
using SafeERC20 for IERC20Metadata;
bytes32 public constant MANAGER_FEE_CLAIMER_ROLE = keccak256("MANAGER_FEE_CLAIMER_ROLE");
bytes32 public constant BRACKET_FEE_CLAIMER_ROLE = keccak256("BRACKET_FEE_CLAIMER_ROLE");
bytes32 public constant NAV_UPDATER_ROLE = keccak256("NAV_UPDATER_ROLE");
bytes32 public constant ADMIN_LITE_ROLE = keccak256("ADMIN_LITE_ROLE");
mapping(bytes32 => WithdrawalStatus) public withdrawals;
mapping(address => Deposit) public lastDeposit;
mapping(uint16 => uint256) public totalWithdrawals;
mapping(uint16 => uint256) public navs;
uint256 public vanityNav;
uint256 public totalDeposits;
uint256 public totalQueuedDeposits;
int256 private totalPendingShares;
uint256 public accruedManagerPerformanceFees;
uint256 public accruedManagerTvlFees;
uint256 public accruedBrktTvlFees;
IERC20Metadata public token;
address public manager;
uint48 public lockFrequency;
uint48 public nextLock;
uint16 public epoch;
uint16 public withdrawDelay;
uint256 private _totalNonMintedShares;
// ╔═══════════════════════════╗
// ║ CONSTRUCTOR & INITIALIZER ║
// ╚═══════════════════════════╝
modifier onlyUnlocked() {
if (isVaultLocked()) revert VaultLocked();
_;
}
modifier onlyLocked() {
if (!isVaultLocked()) revert VaultNotLocked();
_;
}
constructor(address _kycWhitelist) RebasingToken(_kycWhitelist) {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol,
address _token,
address _manager,
address multisig,
uint16 _withdrawDelay,
uint48 _lockFrequency,
address[] memory _accounts,
bytes32[] memory _roles
) external initializer {
if (_manager == address(0) || multisig == address(0) || _token == address(0)) revert ZeroAddress();
uint8 _decimals = IERC20Metadata(_token).decimals();
__AccessControl_init();
__Pausable_init();
__RebasingToken_init(name, symbol, _decimals);
_grantRole(DEFAULT_ADMIN_ROLE, multisig);
uint256 length = _accounts.length;
if (length != _roles.length) revert InvalidRolesLength();
for (uint256 i = 0; i < length; i++) {
if (_accounts[i] == address(0)) revert ZeroAddress();
if (_roles[i] == DEFAULT_ADMIN_ROLE) revert InvalidRole();
_grantRole(_roles[i], _accounts[i]);
}
navs[0] = 10 ** _decimals;
vanityNav = 10 ** _decimals;
token = IERC20Metadata(_token);
manager = _manager;
_setLockFrequency(_lockFrequency);
_setWithdrawDelay(_withdrawDelay);
}
// ╔═══════════════════════════╗
// ║ EXTERNAL ADMIN FUNCTIONS ║
// ╚═══════════════════════════╝
function startVault(uint256 minimumDeposits) external onlyRole(NAV_UPDATER_ROLE) whenNotPaused {
// Get the total deposits to save on gas
uint256 _totalDeposits = totalDeposits;
if (epoch != 0) revert VaultAlreadyStarted();
if (_totalDeposits < minimumDeposits) revert MinimumDepositsNotReached();
// Transfer all the assets to the manager
token.safeTransfer(manager, _totalDeposits);
// Account for the pending shares, with a 1:1 NAV
totalPendingShares = int256(_totalDeposits);
_totalNonMintedShares = _totalDeposits;
// Reset the total deposits
totalDeposits = 0;
epoch = 1;
nextLock = uint48(block.timestamp) + lockFrequency;
// Emit the event
emit VaultStarted(_totalDeposits);
}
function updateNav(uint256 newNav, uint256 managerPerformanceFee, uint256 managerTvlFee, uint256 brktTvlFee) external onlyRole(NAV_UPDATER_ROLE) onlyLocked whenNotPaused {
if (epoch == 0) revert VaultNotStarted();
nextLock += lockFrequency;
// Add to the accrued fees
accruedManagerPerformanceFees += managerPerformanceFee;
accruedManagerTvlFees += managerTvlFee;
accruedBrktTvlFees += brktTvlFee;
uint256 totalFees = managerPerformanceFee + managerTvlFee + brktTvlFee;
// Process deposits and withdrawals
(uint256 clearedDepositAssets, uint256 clearedWithdrawalAssets) = _processDepositsWithdrawals(newNav, totalFees);
// Set the new NAV and move to the next epoch
navs[epoch] = newNav;
vanityNav = newNav;
// Update the epoch
epoch++;
// Emit the event
emit NavUpdated(epoch - 1, newNav, clearedDepositAssets, clearedWithdrawalAssets, managerPerformanceFee, managerTvlFee, brktTvlFee);
}
function claimManagerFees() external onlyRole(MANAGER_FEE_CLAIMER_ROLE) whenNotPaused {
// Transfer the fees from the vault to the claimer
uint256 totalFees = accruedManagerPerformanceFees + accruedManagerTvlFees;
if (totalFees == 0) revert NoFeesToClaim();
token.safeTransfer(msg.sender, totalFees);
// Reset the accrued fees
accruedManagerPerformanceFees = 0;
accruedManagerTvlFees = 0;
}
function claimBrktTvlFees() external onlyRole(BRACKET_FEE_CLAIMER_ROLE) whenNotPaused {
// Transfer the fees from the manager to the claimer
token.safeTransfer(msg.sender, accruedBrktTvlFees);
// Reset the accrued fees
accruedBrktTvlFees = 0;
}
function setManager(address _manager) external onlyRole(ADMIN_LITE_ROLE) whenNotPaused {
if (_manager == address(0)) revert ZeroAddress();
manager = _manager;
}
function setNextLock(uint48 _nextLock) external onlyRole(ADMIN_LITE_ROLE) whenNotPaused {
if (_nextLock <= block.timestamp) revert InvalidLockTime();
nextLock = _nextLock;
}
function setLockFrequency(uint48 _lockFrequency) external onlyRole(ADMIN_LITE_ROLE) whenNotPaused {
_setLockFrequency(_lockFrequency);
}
function setWithdrawDelay(uint16 _withdrawDelay) external onlyRole(ADMIN_LITE_ROLE) whenNotPaused {
_setWithdrawDelay(_withdrawDelay);
}
// This function is used to fix the total non minted shares after an upgrade, will be batched along with the upgrade
function fixTotalNonMintedShares() external onlyRole(ADMIN_LITE_ROLE) whenNotPaused {
require(_totalNonMintedShares == 0, "ALREADY FIXED");
require(totalPendingShares >= 0, "TOTAL PENDING SHARES IS NEGATIVE");
uint256 sumWithdrawals = 0;
uint16 currentEpoch = epoch;
uint16 maxEpoch = currentEpoch + withdrawDelay;
for(uint16 i = currentEpoch; i <= maxEpoch; i++) {
sumWithdrawals += totalWithdrawals[i];
}
_totalNonMintedShares = uint256(totalPendingShares) - sumWithdrawals;
}
function switchPause() external onlyRole(ADMIN_LITE_ROLE) {
if (paused()) {
_unpause();
} else {
_pause();
}
}
function setNameAndSymbol(string memory name_, string memory symbol_) external onlyRole(ADMIN_LITE_ROLE) whenNotPaused {
ERC20Storage storage $;
assembly {
$.slot := 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00
}
$._name = name_;
$._symbol = symbol_;
}
// ╔═══════════════════════════╗
// ║ EXTERNAL USER FUNCTIONS ║
// ╚═══════════════════════════╝
function deposit(uint256 assets, address destination) external whenNotPaused {
if (assets == 0) revert ZeroAmount();
_checkDepositKYC();
// Transfer the assets to the vault
token.safeTransferFrom(msg.sender, address(this), assets);
// Try to clear any pending deposit from past epochs
_clearDeposit(destination);
uint16 _epoch = epoch;
lastDeposit[destination].epoch = _epoch;
if (isVaultLocked()) {
// If the vault is locked, queue the deposit
lastDeposit[destination].queuedAssets += assets;
// Update the total queued deposits
totalQueuedDeposits += assets;
emit PendingDeposit(destination, assets, _epoch + 2);
} else {
// If the vault is unlocked, add the asset to the last deposit assets
lastDeposit[destination].assets += assets;
// Update the total deposits
totalDeposits += assets;
emit PendingDeposit(destination, assets, _epoch + 1);
}
}
function withdraw(uint256 assets, bytes32 salt) external onlyUnlocked whenNotPaused {
if (assets == 0) revert ZeroAmount();
_checkWithdrawalKYC();
// Get the current epoch to memory to save on gas and calculate the claim epoch
uint16 currentEpoch = epoch;
// If the vault is not started, allow users to withdraw instantly
if (currentEpoch == 0) {
_withdrawEpoch0(msg.sender, assets);
return;
}
// Calculate the shares to withdraw
uint256 shares = convertToShares(assets, navs[currentEpoch - 1]);
if (shares == 0) revert ZeroAmount();
uint16 claimEpoch = currentEpoch + withdrawDelay;
// Try to clear any pending deposit from past epochs
_clearDeposit(msg.sender);
// Burn the amount of receipt shares the user wants to withdraw, and add the shares to the total pending shares
totalPendingShares += int256(shares);
_burn(msg.sender, shares);
// Add the withdrawal to the total withdrawals
totalWithdrawals[claimEpoch - 1] += shares;
// Schedule the withdrawal
_scheduleWithdrawal(shares, claimEpoch, salt);
}
function claimWithdrawal(uint256 shares, uint16 claimEpoch, uint256 timestamp, bytes32 salt) external whenNotPaused {
// Hash the withdrawal and check that it is ready
(WithdrawalStatus status, bytes32 digest) = getWithdrawalStatusAndHash(msg.sender, shares, claimEpoch, timestamp, salt);
if (status != WithdrawalStatus.Ready) revert WithdrawalNotReady();
_checkWithdrawalKYC();
// Calculate the amount of assets to send to the user
uint256 assets = convertToAssets(shares, navs[claimEpoch - 1]);
// Full withdrawal, set the withdrawal status to completed
withdrawals[digest] = WithdrawalStatus.Completed;
// Transfer the full amount of assets to the user
token.safeTransfer(msg.sender, assets);
// Emit the event
emit ClaimedWithdrawal(msg.sender, digest, shares, assets, 0);
}
// ╔════════════════════════════════════╗
// ║ PUBLIC VIEW AND PURE FUNCTIONS ║
// ╚════════════════════════════════════╝
function isVaultLocked() public view returns (bool) {
return block.timestamp > nextLock && epoch != 0;
}
function getWithdrawalStatusAndHash(address user, uint256 amount, uint16 claimEpoch, uint256 timestamp, bytes32 salt)
public
view
returns (WithdrawalStatus, bytes32) {
bytes32 digest = hashWithdrawal(user, claimEpoch, amount, timestamp, salt);
WithdrawalStatus status = withdrawals[digest];
if (status == WithdrawalStatus.Queued && epoch >= claimEpoch) {
return (WithdrawalStatus.Ready, digest);
}
return (status, digest);
}
function hashWithdrawal(address user, uint16 claimEpoch, uint256 shares, uint256 timestamp, bytes32 salt) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(user, claimEpoch, shares, timestamp, salt));
}
// ---- RebasingToken functions ----
function pendingMintShares(address account) public view override(IRebasingToken, RebasingToken) returns (uint256) {
Deposit memory _lastDeposit = lastDeposit[account];
// If in the current epoch
if (_lastDeposit.epoch == epoch) {
// We don't know any NAV, so shares can't be accounted for
return 0;
}
// If in the previous epoch
if (_lastDeposit.epoch + 1 == epoch) {
// We know the NAV for the previous epoch, so we can calculate the shares of the assets in the previous epoch but not the queued assets
return convertToShares(_lastDeposit.assets, navs[_lastDeposit.epoch]);
}
// If at least 2 epochs ago, we can calculate the shares of both the assets in the epoch and the queued assets in the epoch after
return convertToShares(_lastDeposit.assets, navs[_lastDeposit.epoch]) + convertToShares(_lastDeposit.queuedAssets, navs[_lastDeposit.epoch + 1]);
}
function inactiveBalanceOf(address account) public view override(IRebasingToken, RebasingToken) returns (uint256) {
Deposit memory _lastDeposit = lastDeposit[account];
// If at least 2 epochs ago, no assets are inactive
if (_lastDeposit.epoch + 1 < epoch) {
return 0;
}
// If in the previous epoch
if (_lastDeposit.epoch < epoch) {
// We know the previous NAV, so only the queued assets are inactive
return _lastDeposit.queuedAssets;
}
// If in the current epoch, both the assets in the epoch and the queued assets in the epoch after are inactive
return _lastDeposit.assets + _lastDeposit.queuedAssets;
}
function totalNonMintedShares() public view override(IRebasingToken, RebasingToken) returns (uint256) {
return _totalNonMintedShares;
}
function inactiveSupply() public view override(IRebasingToken, RebasingToken) returns (uint256) {
return totalDeposits + totalQueuedDeposits;
}
function assetsPerShare() public view override(IRebasingToken, RebasingToken) returns (uint256) {
if (epoch == 0) {
return 10 ** decimals();
} else {
return navs[epoch - 1];
}
}
// ╔═══════════════════════╗
// ║ INTERNAL FUNCTIONS ║
// ╚═══════════════════════╝
function _setLockFrequency(uint48 _lockFrequency) internal {
if (_lockFrequency < 1 hours || _lockFrequency > 30 days) revert InvalidLockFrequency();
lockFrequency = _lockFrequency;
}
function _setWithdrawDelay(uint16 _withdrawDelay) internal {
if (_withdrawDelay == 0 || _withdrawDelay > 30) revert InvalidWithdrawalDelay();
withdrawDelay = _withdrawDelay;
}
function _clearDeposit(address user) internal override {
Deposit memory _lastDeposit = lastDeposit[user];
uint16 _epoch = epoch;
// If the last deposit is the current epoch, we can't clear it yet as we don't have the NAV
if (_lastDeposit.epoch == _epoch) return;
// If both the assets and the queued assets are 0, we don't need to mint but can consider it cleared
if (_lastDeposit.assets == 0 && _lastDeposit.queuedAssets == 0) return;
uint256 shares;
// If the epoch is the previous epoch, we can clear the assets and move the queued assets to the queue
if (_lastDeposit.epoch + 1 == _epoch) {
shares = convertToShares(_lastDeposit.assets, navs[_lastDeposit.epoch]);
// Update the last deposit by moving the queued assets to the assets and updating the epoch
Deposit memory newLastDeposit = Deposit({
epoch: _epoch,
assets: _lastDeposit.queuedAssets,
queuedAssets: 0
});
lastDeposit[user] = newLastDeposit;
} else {
// If the epoch is at least two epochs ago, we can clear both assets and queued assets
shares = convertToShares(_lastDeposit.assets, navs[_lastDeposit.epoch]) + convertToShares(_lastDeposit.queuedAssets, navs[_lastDeposit.epoch + 1]);
// Remove the last deposit for the user
delete lastDeposit[user];
}
_mint(user, shares);
// Remove the shares from the total pending shares as they are now part of the total supply
totalPendingShares -= int256(shares);
_totalNonMintedShares -= shares;
}
function _withdrawEpoch0(address user, uint256 assets) internal {
Deposit memory _lastDeposit = lastDeposit[user];
if (assets > _lastDeposit.assets) revert InsufficientBalance();
// Deduct the assets from the user's deposit and total deposits
lastDeposit[user].assets = _lastDeposit.assets - assets;
totalDeposits -= assets;
token.safeTransfer(user, assets);
emit ClaimedWithdrawal(user, bytes32(uint256(0)), assets, assets, 0);
}
function _scheduleWithdrawal(uint256 shares, uint16 claimEpoch, bytes32 salt) internal {
// Hash the withdrawal
bytes32 digest = hashWithdrawal(msg.sender, claimEpoch, shares, block.timestamp, salt);
// Check that there is no collision, eg: another user withdrawal with the same amount in the same tx with the same salt
if (withdrawals[digest] != WithdrawalStatus.Unset) revert WithdrawalAlreadyQueued();
// Set the withdrawal status to queued
withdrawals[digest] = WithdrawalStatus.Queued;
// Emit the event
emit ScheduledWithdrawal(msg.sender, shares, claimEpoch, block.timestamp, salt);
}
function _processDepositsWithdrawals(uint256 nav, uint256 totalFees) internal returns(uint256, uint256) {
// Calculate total deposits and withdrawals in assets
uint256 depositAssets = totalDeposits;
uint256 depositShares = convertToShares(depositAssets, nav);
uint256 withdrawalShares = totalWithdrawals[epoch];
uint256 withdrawalAssets = convertToAssets(withdrawalShares, nav);
uint256 totalDebit = withdrawalAssets + totalFees;
// Account for the pending shares, add the shares deposited on this epoch and substract the shares that can be now claimed
totalPendingShares += int256(depositShares) - int256(withdrawalShares);
_totalNonMintedShares += depositShares;
// Move the queued deposits to the total deposits
totalDeposits = totalQueuedDeposits;
// Clear the remaining totals for next epoch
delete totalQueuedDeposits;
delete totalWithdrawals[epoch];
// Case 1: More deposits than withdrawals and fees
if (depositAssets > totalDebit) {
uint256 credit = depositAssets - totalDebit;
// Send excess deposits to manager
token.safeTransfer(manager, credit);
return (depositAssets, withdrawalAssets);
}
// Case 2: More withdrawals and fees than deposits
else if (depositAssets < totalDebit) {
uint256 debit = totalDebit - depositAssets;
uint256 available = _getManagerAvailableBalance();
if (available < debit) revert InsufficientManagerFunds();
// Transfer the available balance to the vault
token.safeTransferFrom(manager, address(this), debit);
return (depositAssets, withdrawalAssets);
}
// Case 3: Deposits equal withdrawals and fees - no transfer needed
return (depositAssets, withdrawalAssets);
}
function _getManagerAvailableBalance() internal view returns (uint256) {
uint256 balance = token.balanceOf(manager);
uint256 allowance = token.allowance(manager, address(this));
// Get the minimum of the balance or the allowance
return Math.min(balance, allowance);
}
function _checkDepositKYC() internal view {
if (!KYC_WHITELIST.canDeposit(address(this), msg.sender)) revert KYCFailed();
}
function _checkWithdrawalKYC() internal view {
if (!KYC_WHITELIST.canWithdraw(address(this), msg.sender)) revert KYCFailed();
}
}"
},
"src/vaults/IBracketVaultV2.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IRebasingToken} from "./IRebasingToken.sol";
/**
* @title IBracketVault
* @notice Interface for the BracketVault contract which manages deposits, withdrawals, and NAV updates
*/
interface IBracketVaultV2 is IRebasingToken {
error KYCFailed();
error InsufficientBalance();
error InsufficientManagerFunds();
error InvalidWithdrawalDelay();
error InvalidLockFrequency();
error InvalidLength();
error InvalidLockTime();
error InvalidRole();
error InvalidRolesLength();
error MinimumDepositsNotReached();
error NoFeesToClaim();
error VaultAlreadyStarted();
error VaultNotStarted();
error VaultLocked();
error VaultNotLocked();
error WithdrawalAlreadyQueued();
error WithdrawalNotReady();
error ZeroAmount();
/**
* @notice Represents the status of a withdrawal request
* @param Unset Initial state, withdrawal has not been requested
* @param Queued Withdrawal has been requested but not yet ready for claiming
* @param Ready Withdrawal is ready to be claimed
* @param Partial Withdrawal was partially processed due to insufficient funds
* @param Completed Withdrawal has been fully processed and claimed
*/
enum WithdrawalStatus {
Unset,
Queued,
Ready,
Partial,
Completed
}
/**
* @notice Structure representing a user's deposit
* @param epoch The epoch when the deposit was made
* @param assets The amount of assets deposited
* @param queuedAssets The amount of assets queued for withdrawal
*/
struct Deposit {
uint16 epoch;
uint256 assets;
uint256 queuedAssets;
}
/**
* @notice Emitted when a deposit happens
* @param user Address of the user making the deposit
* @param assets Amount of assets to deposit
* @param epoch Epoch when the deposit will be credited
*/
event PendingDeposit(address indexed user, uint256 assets, uint16 epoch);
/**
* @notice Emitted when a withdrawal is scheduled
* @param user Address of the user scheduling the withdrawal
* @param shares Amount of shares to withdraw
* @param claimEpoch Epoch when the withdrawal can be claimed
* @param timestamp Time when the withdrawal was scheduled
* @param salt Random value to prevent withdrawal hash collisions
*/
event ScheduledWithdrawal(address indexed user, uint256 shares, uint16 claimEpoch, uint256 timestamp, bytes32 salt);
/**
* @notice Emitted when a withdrawal is claimed
* @param user Address of the user claiming the withdrawal
* @param withdrawalId Unique identifier of the withdrawal
* @param shares Amount of shares withdrawn
* @param assets Amount of assets received
* @param remainingAssets Amount of assets still owed if partial withdrawal
*/
event ClaimedWithdrawal(
address indexed user, bytes32 withdrawalId, uint256 shares, uint256 assets, uint256 remainingAssets
);
/**
* @notice Emitted when NAV is updated
* @param epoch Current epoch number
* @param nav New NAV value
* @param clearedDeposits Total deposits processed
* @param clearedWithdrawals Total withdrawals processed
* @param managerPerformanceFee Performance fee for the manager
* @param managerTvlFee TVL-based fee for the manager
* @param brktTvlFee TVL-based fee for BRKT
*/
event NavUpdated(
uint256 indexed epoch,
uint256 nav,
uint256 clearedDeposits,
uint256 clearedWithdrawals,
uint256 managerPerformanceFee,
uint256 managerTvlFee,
uint256 brktTvlFee
);
/**
* @notice Emitted when vanity NAV is updated
* @param epoch Current epoch number
* @param newNav New vanity NAV value
*/
event VanityNavUpdated(uint256 indexed epoch, uint256 newNav);
/**
* @notice Emitted when vault is started
* @param totalDeposits Total initial deposits
*/
event VaultStarted(uint256 totalDeposits);
/**
* @notice Starts the vault, transferring initial deposits to the manager
* @param minimumDeposits Minimum number of deposits required to start the vault
* @dev Can only be called by NAV_UPDATER_ROLE
*/
function startVault(uint256 minimumDeposits) external;
/**
* @notice Updates the NAV and processes deposits/withdrawals
* @param newNav New NAV value
* @param managerPerformanceFee Performance fee for the manager
* @param managerTvlFee TVL-based fee for the manager
* @param brktTvlFee TVL-based fee for BRKT
*/
function updateNav(uint256 newNav, uint256 managerPerformanceFee, uint256 managerTvlFee, uint256 brktTvlFee) external;
/**
* @notice Claims accrued manager fees
*/
function claimManagerFees() external;
/**
* @notice Claims accrued BRKT TVL fees
*/
function claimBrktTvlFees() external;
/**
* @notice Deposits assets into the vault
* @param assets Amount of assets to deposit
* @param destination Address to send the deposit to
*/
function deposit(uint256 assets, address destination) external;
/**
* @notice Initiates a withdrawal request
* @param assets Amount of assets to withdraw
* @param salt Random value to prevent withdrawal hash collisions
*/
function withdraw(uint256 assets, bytes32 salt) external;
/**
* @notice Claims a previously scheduled withdrawal
* @param shares Amount of shares to withdraw
* @param claimEpoch Epoch when the withdrawal can be claimed
* @param timestamp Time when the withdrawal was scheduled
* @param salt Random value used in the withdrawal request
*/
function claimWithdrawal(uint256 shares, uint16 claimEpoch, uint256 timestamp, bytes32 salt) external;
/**
* @notice Returns the status and hash of a withdrawal request
* @param user Address of the user
* @param amount Amount of shares to withdraw
* @param claimEpoch Epoch when the withdrawal can be claimed
* @param timestamp Time when the withdrawal was scheduled
* @param salt Random value used in the withdrawal request
* @return WithdrawalStatus status of the withdrawal
* @return bytes32 hash of the withdrawal
*/
function getWithdrawalStatusAndHash(address user, uint256 amount, uint16 claimEpoch, uint256 timestamp, bytes32 salt)
external
view
returns (WithdrawalStatus, bytes32);
// /**
// * @notice Computes the hash of a withdrawal request
// * @param user Address of the user
// * @param claimEpoch Epoch when the withdrawal can be claimed
// * @param shares Amount of shares to withdraw
// * @param timestamp Time when the withdrawal was scheduled
// * @param salt Random value to prevent hash collisions
// * @return Hash of the withdrawal request
// */
// function hashWithdrawal(address user, uint16 claimEpoch, uint256 shares, uint256 timestamp, bytes32 salt)
// internal
// pure
// returns (bytes32);
/**
* @notice epoch accessor
*/
function epoch() external view returns (uint16);
/**
* @notice withdrawDelay accessor
*/
function withdrawDelay() external view returns (uint16);
}
"
},
"src/vaults/RebasingToken.sol": {
"content": "pragma solidity ^0.8.20;
import {IRebasingToken, IERC20Metadata} from "./IRebasingToken.sol";
import {IERC20, ERC20Upgradeable} from "openzeppelin-contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {KYCWhitelist} from "./KYCWhitelist.sol";
/**
* @title RebasingToken
* @notice Abstract contract for rebasing tokens that automatically adjust balances based on an assets-per-share ratio
* @dev Implements IRebasingToken interface
*/
abstract contract RebasingToken is ERC20Upgradeable, IRebasingToken {
error TransferNotAllowed();
error ZeroAddress();
KYCWhitelist public immutable KYC_WHITELIST;
uint8 private _decimals;
constructor(address kycWhitelist) {
if (kycWhitelist == address(0)) revert ZeroAddress();
KYC_WHITELIST = KYCWhitelist(kycWhitelist);
}
function __RebasingToken_init(string memory name, string memory symbol, uint8 tokenDecimals) internal onlyInitializing {
__ERC20_init(name, symbol);
_decimals = tokenDecimals;
}
function mintedSharesOf(address account) public view returns (uint256) {
return super.balanceOf(account);
}
function pendingMintShares(address account) public view virtual returns (uint256);
function sharesOf(address account) public view returns (uint256) {
return mintedSharesOf(account) + pendingMintShares(account);
}
function totalMintedShares() public view returns (uint256) {
return super.totalSupply();
}
function totalNonMintedShares() public view virtual returns (uint256);
function totalShares() public view returns (uint256) {
return totalMintedShares() + totalNonMintedShares();
}
function activeBalanceOf(address account) public view returns (uint256) {
return convertToAssets(sharesOf(account), assetsPerShare());
}
function inactiveBalanceOf(address account) public view virtual returns (uint256);
function balanceOf(address account) public view override(IERC20, ERC20Upgradeable) returns (uint256) {
return activeBalanceOf(account);
}
function activeSupply() public view returns (uint256) {
return convertToAssets(totalShares(), assetsPerShare());
}
function inactiveSupply() public view virtual returns (uint256);
function totalSupply() public view override(IERC20, ERC20Upgradeable) returns (uint256) {
return activeSupply();
}
function convertToShares(uint256 assets, uint256 _assetsPerShare) public view virtual returns (uint256) {
return Math.mulDiv(assets, 10 ** _decimals, _assetsPerShare);
}
function convertToShares(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets, assetsPerShare());
}
function convertToAssets(uint256 shares, uint256 _assetsPerShare) public view virtual returns (uint256) {
return Math.mulDiv(shares, _assetsPerShare, 10 ** _decimals);
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares, assetsPerShare());
}
function transfer(address to, uint256 value) public virtual override (IERC20, ERC20Upgradeable) returns (bool) {
uint256 shares = convertToShares(value, assetsPerShare());
return super.transfer(to, shares);
}
function transferFrom(address from, address to, uint256 value) public virtual override (IERC20, ERC20Upgradeable) returns (bool) {
uint256 shares = convertToShares(value, assetsPerShare());
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, shares);
return true;
}
function assetsPerShare() public view virtual returns (uint256);
function _clearDeposit(address user) internal virtual;
function _update(address from, address to, uint256 value) internal override(ERC20Upgradeable) {
if (!KYC_WHITELIST.canTransfer(address(this), from, to)) revert TransferNotAllowed();
if (from != address(0) && to != address(0)) {
// If it's not a mint or burn, clear the deposits
_clearDeposit(from);
}
super._update(from, to, value);
}
function decimals() public view override(IERC20Metadata, ERC20Upgradeable) returns (uint8) {
return _decimals;
}
}"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../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, IAccessControl, ERC165Upgradeable {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
struct AccessControlStorage {
mapping(bytes32 role => RoleData) _roles;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;
function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
assembly {
$.slot := AccessControlStorageLocation
}
}
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @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 returns (bytes32) {
AccessControlStorage storage $ = _getAccessControlStorage();
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 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 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 `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
AccessControlStorage storage $ = _getAccessControlStorage();
bytes32 previousAdminRole = getRoleAdmin(role);
$._roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (!hasRole(role, account)) {
$._roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (hasRole(role, account)) {
$._roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../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 {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @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);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
PausableStorage storage $ = _getPausableStorage();
$._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();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// 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);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// 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;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
"content": "// 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;
Submitted on: 2025-09-19 19:13:33
Comments
Log in to comment.
No comments yet.