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": {
"contracts/extensions/USDOExpressV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "./USDOExpressPausable.sol";
import "./USDOMintRedeemLimiterV2.sol";
import "./DoubleQueueModified.sol";
import {IUSDO} from "../interfaces/IUSDO.sol";
import {ICUSDO} from "../interfaces/ICUSDO.sol";
import "../interfaces/IRedemption.sol";
import "../interfaces/IAssetRegistry.sol";
enum TxType {
MINT,
REDEEM,
INSTANT_REDEEM
}
contract USDOExpressV2 is UUPSUpgradeable, AccessControlUpgradeable, USDOExpressPausable, USDOMintRedeemLimiter {
using MathUpgradeable for uint256;
using DoubleQueueModified for DoubleQueueModified.BytesDeque;
// Roles
bytes32 public constant MULTIPLIER_ROLE = keccak256("MULTIPLIER_ROLE");
bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");
bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE");
bytes32 public constant UPGRADE_ROLE = keccak256("UPGRADE_ROLE");
bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
// APY in base points, scaled by 1e4 (e.g., 100 = 1%)
uint256 public _apy; // 500 => 5%
// fee rate for mint, scaled by 1e4, e.g., 100 stands for 1%
uint256 public _mintFeeRate;
uint256 public _redeemFeeRate;
// constants for base points and scaling
uint256 private constant _BPS_BASE = 1e4;
uint256 private constant _BASE = 1e18;
// daily bonus multiplier increment, scaled by 1e18
uint256 public _increment;
// The last time the bonus multiplier was updated
uint256 public _lastUpdateTS;
// Time buffer for the operator to update the bonus multiplier
uint256 public _timeBuffer;
// core token addresses
IUSDO public _usdo;
address public _usdc;
// #previous: _tbill
address public RESERVE1;
// the address to receive the tokens
address public _treasury;
// the address to receive the fees
address public _feeTo;
// Asset registry for pluggable asset management #previous: _buidl;
IAssetRegistry public _assetRegistry;
// for instant redeem - pluggable redemption contract, #previous: _buidlRedemption
IRedemption public _redemptionContract;
// cUSDO contract , #previous: _buidlTreasury
ICUSDO public _cusdo;
// check if the user has deposited before
mapping(address => bool) public _firstDeposit;
// kyc list
mapping(address => bool) public _kycList;
// Queue for redemption requests
DoubleQueueModified.BytesDeque private _redemptionQueue;
// Track redemption amounts for users in the queue
mapping(address => uint256) private _redemptionInfo;
// fee rate for instant redeem, scaled by 1e4, e.g., 100 stands for 1%
uint256 public _instantRedeemFeeRate;
// Events
event UpdateAPY(uint256 apy, uint256 increment);
event UpdateCusdo(address cusdo);
event UpdateMintFeeRate(uint256 fee);
event UpdateRedeemFeeRate(uint256 fee);
event UpdateInstantRedeemFee(uint256 fee);
event UpdateTreasury(address treasury);
event UpdateFeeTo(address feeTo);
event UpdateTimeBuffer(uint256 timeBuffer);
event InstantMint(
address indexed underlying,
address indexed from,
address indexed to,
uint256 reqAmt,
uint256 receiveAmt,
uint256 fee
);
event InstantMintAndWrap(
address indexed underlying,
address indexed from,
address indexed to,
uint256 reqAmt,
uint256 usdoAmt,
uint256 cusdoAmt,
uint256 fee
);
event USDOKycGranted(address[] addresses);
event USDOKycRevoked(address[] addresses);
event InstantRedeem(
address indexed from,
address indexed to,
uint256 reqAmt,
uint256 receiveAmt,
uint256 fee,
uint256 payout,
uint256 usycFee,
uint256 minUsdcOut
);
event ManualRedeem(address indexed from, uint256 reqAmt, uint256 receiveAmt, uint256 fee);
event UpdateFirstDeposit(address indexed account, bool flag);
// Queue-related events
event AddToRedemptionQueue(address indexed from, address indexed to, uint256 usdoAmt, bytes32 id);
event ProcessRedeem(
address indexed from,
address indexed to,
uint256 usdoAmt,
uint256 usdcAmt,
uint256 fee,
bytes32 id
);
event ProcessRedemptionQueue(uint256 totalRedeemAssets, uint256 totalBurnUsdo, uint256 totalFees);
event ProcessRedemptionCancel(address indexed from, address indexed to, uint256 usdoAmt, bytes32 id);
event Cancel(uint256 len, uint256 totalUsdo);
event SetRedemption(address redemptionContract);
event AssetRegistryUpdated(address indexed newRegistry);
event OffRamp(address indexed to, uint256 amount);
error USDOExpressTooEarly(uint256 amount);
error USDOExpressZeroAddress();
error USDOExpressTokenNotSupported(address token);
error USDOExpressReceiveUSDCFailed(uint256 amount, uint256 received);
error MintLessThanMinimum(uint256 amount, uint256 minimum);
error TotalSupplyCapExceeded();
error FirstDepositLessThanRequired(uint256 amount, uint256 minimum);
error USDOExpressNotInKycList(address from, address to);
error USDOExpressInvalidInput(uint256 input);
error USDOExpressInsufficientLiquidity(uint256 required, uint256 available);
error InsufficientOutput(uint256 received, uint256 minimum);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract.
* @param usdo Address of the USDO contract.
* @param usdc Address of the USDC contract.
*/
function initialize(
address usdo,
address cusdo,
address usdc,
address treasury,
address feeTo,
address maintainer,
address operator,
address admin,
address assetRegistry,
USDOMintRedeemLimiterCfg memory cfg
) external initializer {
__AccessControl_init();
__UUPSUpgradeable_init();
_usdo = IUSDO(usdo);
_cusdo = ICUSDO(cusdo);
_usdc = usdc;
_treasury = treasury;
_feeTo = feeTo;
_assetRegistry = IAssetRegistry(assetRegistry);
__USDOMintRedeemLimiter_init(
cfg.mintMinimum,
cfg.mintLimit,
cfg.mintDuration,
cfg.redeemMinimum,
cfg.redeemLimit,
cfg.redeemDuration,
cfg.firstDepositAmount
);
// Grant roles
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(UPGRADE_ROLE, admin);
_grantRole(WHITELIST_ROLE, maintainer);
_grantRole(MAINTAINER_ROLE, maintainer);
_grantRole(MULTIPLIER_ROLE, operator);
_grantRole(PAUSE_ROLE, operator);
_grantRole(OPERATOR_ROLE, operator);
}
function _authorizeUpgrade(address) internal override onlyRole(UPGRADE_ROLE) {}
/**
* @notice Updates the APY.
* @dev This function can only be called by the owner.
* @param newAPY The new APY value in base points, apy example: 514 = 5.14%
*/
function updateAPY(uint256 newAPY) external onlyRole(MAINTAINER_ROLE) {
_apy = newAPY;
// 140821917808219
// 140821917808219.1780821918
_increment = newAPY.mulDiv(_BASE, 365) / (_BPS_BASE);
emit UpdateAPY(newAPY, _increment);
}
/**
* @notice Update the cUSDO contract.
* @param cusdo The address of the cUSDO contract.
*/
function updateCusdo(address cusdo) external onlyRole(MAINTAINER_ROLE) {
if (cusdo == address(0)) revert USDOExpressZeroAddress();
_cusdo = ICUSDO(cusdo);
emit UpdateCusdo(cusdo);
}
/**
* @notice Update the asset registry address
* @param newRegistry The new asset registry address
*/
function setAssetRegistry(address newRegistry) external onlyRole(MAINTAINER_ROLE) {
if (newRegistry == address(0)) revert USDOExpressZeroAddress();
_assetRegistry = IAssetRegistry(newRegistry);
emit AssetRegistryUpdated(newRegistry);
}
/**
*@notice Will be used to update the bonus multiplier in the USDO contract.
*/
function addBonusMultiplier() external onlyRole(MULTIPLIER_ROLE) {
if (_lastUpdateTS != 0) {
if (block.timestamp < _lastUpdateTS + _timeBuffer) revert USDOExpressTooEarly(block.timestamp);
}
_usdo.addBonusMultiplier(_increment);
_lastUpdateTS = block.timestamp;
}
/**
* @notice Set the time buffer for the operator to update the bonus multiplier
* @dev Can only be called by the contract operator
* @param timeBuffer Time buffer in seconds
*/
function updateTimeBuffer(uint256 timeBuffer) external onlyRole(MAINTAINER_ROLE) {
_timeBuffer = timeBuffer;
emit UpdateTimeBuffer(timeBuffer);
}
/**
* @notice Updates the fee percentage.
* @dev This function can only be called by the operator.
* @param fee The new fee percentage in base points.
*/
function updateMintFee(uint256 fee) external onlyRole(MAINTAINER_ROLE) {
_mintFeeRate = fee;
emit UpdateMintFeeRate(fee);
}
/**
* @notice Updates the fee percentage for redeem.
* @dev This function can only be called by the operator.
* @param fee The new fee percentage in base points.
*/
function updateRedeemFee(uint256 fee) external onlyRole(MAINTAINER_ROLE) {
_redeemFeeRate = fee;
emit UpdateRedeemFeeRate(fee);
}
/**
* @notice Updates the fee percentage for instant redeem.
* @dev This function can only be called by the operator.
* @param fee The new fee percentage in base points.
*/
function updateInstantRedeemFee(uint256 fee) external onlyRole(MAINTAINER_ROLE) {
_instantRedeemFeeRate = fee;
emit UpdateInstantRedeemFee(fee);
}
/**
* @notice Allows a whitelisted user to perform an instant mint.
* @param underlying The address of the token to mint USDO from.
* @param to The address to mint the USDO to.
* @param amt The supplied amount of the underlying token.
*/
function instantMint(address underlying, address to, uint256 amt) external whenNotPausedMint {
address from = _msgSender();
if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);
(uint256 usdoAmtCurr, uint256 fee) = _instantMintInternal(underlying, from, to, to, amt);
emit InstantMint(underlying, from, to, amt, usdoAmtCurr, fee);
}
/**
* @notice Allows a whitelisted user to perform an instant mint.
* @param underlying The address of the token to mint USDO from.
* @param to The address to mint the USDO to.
* @param amt The supplied amount of the underlying token.
*/
function instantMintAndWrap(address underlying, address to, uint256 amt) external whenNotPausedMint {
address from = _msgSender();
if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);
(uint256 usdoAmtCurr, uint256 fee) = _instantMintInternal(underlying, from, address(this), to, amt);
_usdo.approve(address(_cusdo), usdoAmtCurr);
uint256 cusdoAmt = _cusdo.deposit(usdoAmtCurr, to);
emit InstantMint(underlying, from, to, amt, usdoAmtCurr, fee);
emit InstantMintAndWrap(underlying, from, to, amt, usdoAmtCurr, cusdoAmt, fee);
}
/**
* @notice Allows a whitelisted user to perform an instant redeem with slippage protection.
* @dev Will convert USDO to USDC using the configured redemption contract.
* @param to The address to redeem the USDC to.
* @param amt The requested amount of USDO to redeem.
* @param minUsdcOut Minimum USDC amount to receive (slippage protection).
*/
function instantRedeemSelf(address to, uint256 amt, uint256 minUsdcOut) external whenNotPausedRedeem {
address from = _msgSender();
if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);
_checkRedeemLimit(amt);
// 1. burn the USDO
_usdo.burn(from, amt);
// 2. calculate the USDO amount into USDC and request redemption
uint256 usdcNeeded = convertToUnderlying(_usdc, amt);
// 3. redeem through the redemption contract and process
(uint256 payout, uint256 redemptionFee, ) = _redemptionContract.redeemFor(from, usdcNeeded);
// 4. calculate fees
uint256 feeInUsdc = txsFee(usdcNeeded, TxType.INSTANT_REDEEM);
uint256 usdcToUser = payout - feeInUsdc;
// 5. slippage protection
if (minUsdcOut > 0 && usdcToUser < minUsdcOut) {
revert InsufficientOutput(usdcToUser, minUsdcOut);
}
// 6. transfer USDC fee to feeTo and the rest to user
_distributeUsdc(to, usdcToUser, feeInUsdc);
emit InstantRedeem(from, to, amt, usdcToUser, feeInUsdc, payout, redemptionFee, minUsdcOut);
}
/**
* @notice Queue a redemption request for manual processing.
* @dev The redemption request will be put into a queue and processed later when sufficient USDC is available.
* @param to The address to redeem the USDC to.
* @param amt The requested amount of USDO to redeem.
*/
function redeemRequest(address to, uint256 amt) external whenNotPausedRedeem {
address from = _msgSender();
if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);
_checkRedeemLimit(amt);
// Burn USDO from the user
_usdo.burn(from, amt);
_redemptionInfo[to] += amt;
bytes32 id = keccak256(abi.encode(from, to, amt, block.timestamp, _redemptionQueue.length()));
bytes memory data = abi.encode(from, to, amt, id);
_redemptionQueue.pushBack(data);
emit AddToRedemptionQueue(from, to, amt, id);
}
/**
* @notice The redemption request will be processed manually.
* @param amt The requested amount of USDO to redeem.
*/
function redeem(uint256 amt) external whenNotPausedRedeem {
address from = _msgSender();
if (!_kycList[from]) revert USDOExpressNotInKycList(from, from);
_checkRedeemLimit(amt);
_usdo.burn(from, amt);
(uint256 feeAmt, uint256 usdcAmt, ) = previewRedeem(amt, false);
emit ManualRedeem(from, amt, usdcAmt, feeAmt);
}
/**
* @notice Cancel the first _len redemption requests in the queue.
* @dev Only operators can call this function.
* @param _len The length of the cancel requests.
*/
function cancel(uint256 _len) external onlyRole(MAINTAINER_ROLE) {
if (_redemptionQueue.empty()) revert USDOExpressInvalidInput(0);
if (_len > _redemptionQueue.length()) revert USDOExpressInvalidInput(_len);
uint256 totalUsdo;
uint256 originalLen = _len;
while (_len > 0) {
bytes memory data = _redemptionQueue.popFront();
(address sender, address receiver, uint256 usdoAmt, bytes32 prevId) = _decodeData(data);
unchecked {
totalUsdo += usdoAmt;
_redemptionInfo[receiver] -= usdoAmt;
_len--;
}
// Mint USDO back to the user
_usdo.mint(sender, usdoAmt);
emit ProcessRedemptionCancel(sender, receiver, usdoAmt, prevId);
}
emit Cancel(originalLen, totalUsdo);
}
/**
* @notice Process the redemption queue.
* @dev Only operators can call this function.
* @param _len The length of the queue to process, 0 means process all.
*/
function processRedemptionQueue(uint256 _len) external onlyRole(OPERATOR_ROLE) {
uint256 length = _redemptionQueue.length();
if (length == 0) revert USDOExpressInvalidInput(0);
if (_len > length) revert USDOExpressInvalidInput(_len);
if (_len == 0) _len = length;
uint256 totalRedeemAssets;
uint256 totalBurnUsdo;
uint256 totalFees;
for (uint count = 0; count < _len; ) {
bytes memory data = _redemptionQueue.front();
(address sender, address receiver, uint256 usdoAmt, bytes32 prevId) = _decodeData(data);
if (!_kycList[sender] || !_kycList[receiver]) revert USDOExpressNotInKycList(sender, receiver);
// Convert USDO to USDC amount
uint256 usdcAmt = convertToUnderlying(_usdc, usdoAmt);
// Check if we have enough USDC liquidity
uint256 availableLiquidity = getTokenBalance(_usdc);
if (usdcAmt > availableLiquidity) {
break; // Stop processing if not enough liquidity
}
// Calculate fees
uint256 feeInUsdc = txsFee(usdcAmt, TxType.REDEEM);
uint256 usdcToUser = usdcAmt - feeInUsdc;
// Remove from queue
_redemptionQueue.popFront();
unchecked {
++count;
totalRedeemAssets += usdcToUser;
totalBurnUsdo += usdoAmt;
totalFees += feeInUsdc;
_redemptionInfo[receiver] -= usdoAmt;
}
_distributeUsdc(receiver, usdcToUser, feeInUsdc);
emit ProcessRedeem(sender, receiver, usdoAmt, usdcToUser, feeInUsdc, prevId);
}
emit ProcessRedemptionQueue(totalRedeemAssets, totalBurnUsdo, totalFees);
}
function _distributeUsdc(address to, uint256 usdcToUser, uint256 fee) private {
if (fee > 0) SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_usdc), _feeTo, fee);
SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_usdc), to, usdcToUser);
}
/**
* @notice Previews the instant redeem amounts.
* @dev // USDC (6 decimals) to USDO (18 decimals), to scale to USDCO: amount * (10 ** (usdoDecimals - usdcDecimals));
* @param token The token to provide the value in.
* @param amt The amount of the token to convert.
* @return usdoAmt The value of the token in USDO.
*/
function convertFromUnderlying(address token, uint256 amt) public view returns (uint256 usdoAmt) {
return _assetRegistry.convertFromUnderlying(token, amt);
}
function convertToUnderlying(address token, uint256 usdoAmt) public view returns (uint256 amt) {
return _assetRegistry.convertToUnderlying(token, usdoAmt);
}
function updateTreasury(address treasury) external onlyRole(MAINTAINER_ROLE) {
if (treasury == address(0)) revert USDOExpressZeroAddress();
_treasury = treasury;
emit UpdateTreasury(treasury);
}
function updateFeeTo(address feeTo) external onlyRole(MAINTAINER_ROLE) {
if (feeTo == address(0)) revert USDOExpressZeroAddress();
_feeTo = feeTo;
emit UpdateFeeTo(feeTo);
}
function txsFee(uint256 amt, TxType txType) public view returns (uint256 fee) {
uint256 feeRate;
if (txType == TxType.MINT) {
feeRate = _mintFeeRate;
} else if (txType == TxType.REDEEM) {
feeRate = _redeemFeeRate;
} else if (txType == TxType.INSTANT_REDEEM) {
feeRate = _instantRedeemFeeRate;
}
fee = (amt * feeRate) / _BPS_BASE;
}
/**
* @notice Previews the instant mint amounts.
* @param usdoAmt The amount of USDO requested for minting.
* @return usdoAmtCurr The amount of USDO minted with the current bonus multiplier.
* @return usdoAmtNext The amount of USDO minted with the next bonus multiplier.
*/
function previewIssuance(uint256 usdoAmt) public view returns (uint256 usdoAmtCurr, uint256 usdoAmtNext) {
(uint256 curr, uint256 next) = getBonusMultiplier();
usdoAmtCurr = usdoAmt.mulDiv(curr, next);
usdoAmtNext = usdoAmtCurr.mulDiv(next, curr);
}
function getBonusMultiplier() public view returns (uint256 curr, uint256 next) {
curr = _usdo.bonusMultiplier();
next = curr + _increment;
}
function previewMint(
address underlying,
uint256 amt
) public view returns (uint256 netAmt, uint256 fee, uint256 usdoAmtCurr, uint256 usdoAmtNext) {
fee = txsFee(amt, TxType.MINT);
netAmt = amt - fee;
uint256 usdoAmt = convertFromUnderlying(underlying, netAmt);
(usdoAmtCurr, usdoAmtNext) = previewIssuance(usdoAmt);
}
function previewRedeem(
uint256 amt,
bool isInstant
) public view returns (uint256 feeAmt, uint256 usdcAmt, uint256 extraFee) {
TxType txType = isInstant ? TxType.INSTANT_REDEEM : TxType.REDEEM;
uint256 feeInUsdo = txsFee(amt, txType);
if (isInstant && address(_redemptionContract) != address(0)) {
// For instant redemption, include fees from the redemption contract
uint256 usdcNeeded = convertToUnderlying(_usdc, amt);
// Get redemption preview (payout and redemption fee)
(uint256 redemptionPayout, uint256 redemptionFee, ) = _redemptionContract.previewRedeem(usdcNeeded);
// Calculate USDOExpress fee in USDC
feeAmt = convertToUnderlying(_usdc, feeInUsdo);
// Redemption contract fee (extraFee)
extraFee = redemptionFee;
// User receives: redemption payout - USDOExpress fee
usdcAmt = redemptionPayout - feeAmt;
} else {
// For manual redemption, only USDOExpress fees apply
feeAmt = convertToUnderlying(_usdc, feeInUsdo);
usdcAmt = convertToUnderlying(_usdc, amt - feeInUsdo);
extraFee = 0; // No redemption contract fees for manual redemption
}
}
/**
* @notice Set the redemption contract and token addresses.
* @param redemptionContract Address of the redemption contract.
*/
function setRedemption(address redemptionContract) external onlyRole(MAINTAINER_ROLE) {
_redemptionContract = IRedemption(redemptionContract);
emit SetRedemption(redemptionContract);
}
/**
* @notice Retrieve the on-chain assets amount.
* @param token The address of the token.
* @return assetAmt Amount of onchain usdc.
*/
function getTokenBalance(address token) public view returns (uint256 assetAmt) {
return IERC20Upgradeable(token).balanceOf(address(this));
}
/**
* @notice Update the mint status of the account.
*/
function updateFirstDeposit(address account, bool flag) external onlyRole(MAINTAINER_ROLE) {
_firstDeposit[account] = flag;
emit UpdateFirstDeposit(account, flag);
}
/**
* @notice Retrieve redemption queue information for a given index.
* @param _index Index to retrieve data from.
* @return sender The sender's address.
* @return receiver The receiver's address.
* @return usdoAmt The number of USDO.
* @return id The ID associated with the redemption.
*/
function getRedemptionQueueInfo(
uint256 _index
) external view returns (address sender, address receiver, uint256 usdoAmt, bytes32 id) {
if (_redemptionQueue.empty() || _index > _redemptionQueue.length() - 1) {
return (address(0), address(0), 0, 0x0);
}
bytes memory data = bytes(_redemptionQueue.at(_index));
(sender, receiver, usdoAmt, id) = _decodeData(data);
}
/**
* @notice Retrieve redemption information for a specific user that is in the queue.
* @param _user Address of the user.
* @return usdoAmt Number of USDO associated with the user.
*/
function getRedemptionUserInfo(address _user) external view returns (uint256 usdoAmt) {
return _redemptionInfo[_user];
}
/**
* @notice Retrieve the length of the redemption queue.
* @return Length of the redemption queue.
*/
function getRedemptionQueueLength() external view returns (uint256) {
return _redemptionQueue.length();
}
/*//////////////////////////////////////////////////////////////
USDOExpressPausable functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Pauses minting
*/
function pauseMint() external onlyRole(PAUSE_ROLE) {
_pauseMint();
}
/**
* @notice Unpauses minting
*/
function unpauseMint() external onlyRole(PAUSE_ROLE) {
_unpauseMint();
}
/**
* @notice Pauses redeeming
*/
function pauseRedeem() external onlyRole(PAUSE_ROLE) {
_pauseRedeem();
}
/**
* @notice Unpauses redeeming
*/
function unpauseRedeem() external onlyRole(PAUSE_ROLE) {
_unpauseRedeem();
}
/*//////////////////////////////////////////////////////////////
USDOMintRedeemLimiter functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Set the mint minimum in USDO equivalent.
* @dev Amount should be in 18 decimals (USDO decimals) for accurate comparison across different underlying assets
*/
function setMintMinimum(uint256 mintMinimum) external onlyRole(MAINTAINER_ROLE) {
_setMintMinimum(mintMinimum);
}
/**
* @notice Set the mint limit for a certain duration in seconds, etc 8400s.
*/
function setMintDuration(uint256 mintDuration) external onlyRole(MAINTAINER_ROLE) {
_setMintDuration(mintDuration);
}
/**
* @notice Set the mint limit for a certain duration in seconds.
*/
function setMintLimit(uint256 mintLimit) external onlyRole(MAINTAINER_ROLE) {
_setMintLimit(mintLimit);
}
/**
* @notice Set the redeem minimum in USDO.
* @dev with 18 decimals
*/
function setRedeemMinimum(uint256 redeemMinimum) external onlyRole(MAINTAINER_ROLE) {
_setRedeemMinimum(redeemMinimum);
}
/**
* @notice Set the redeem duration for a certain duration in seconds, etc 8400s.
*/
function setRedeemDuration(uint256 redeemDuration) external onlyRole(MAINTAINER_ROLE) {
_setRedeemDuration(redeemDuration);
}
/**
* @notice Set the redeem limit for a certain duration in seconds.
*/
function setRedeemLimit(uint256 redeemLimit) external onlyRole(MAINTAINER_ROLE) {
_setRedeemLimit(redeemLimit);
}
/**
* @notice Set the first deposit amount for the account.
* @dev Amount should be in 18 decimals (USDO decimals) for accurate comparison across different underlying assets
* @param amount The amount of the first deposit in USDO equivalent.
*/
function setFirstDepositAmount(uint256 amount) external onlyRole(MAINTAINER_ROLE) {
_setFirstDepositAmount(amount);
}
/**
* @notice Grant KYC to the address.
* @param _addresses The address to grant KYC.
*/
function grantKycInBulk(address[] calldata _addresses) external onlyRole(WHITELIST_ROLE) {
for (uint256 i = 0; i < _addresses.length; i++) {
_kycList[_addresses[i]] = true;
}
emit USDOKycGranted(_addresses);
}
/**
* @notice Revoke KYC to the address.
* @param _addresses The address to revoke KYC.
*/
function revokeKycInBulk(address[] calldata _addresses) external onlyRole(WHITELIST_ROLE) {
for (uint256 i = 0; i < _addresses.length; i++) {
_kycList[_addresses[i]] = false;
}
emit USDOKycRevoked(_addresses);
}
/**
* @dev transfer underlying from vault to treasury, only operator can call this function
* @param amt the amount of the token to transfer
*/
function offRamp(uint256 amt) external onlyRole(OPERATOR_ROLE) {
SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_usdc), _treasury, amt);
emit OffRamp(_treasury, amt);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @dev Decodes a given data bytes into its components.
* @param _data Encoded data bytes.
* @return sender Sender's address.
* @return receiver Receiver's address.
* @return usdoAmt Number of USDO.
* @return prevId Previous ID.
*/
function _decodeData(
bytes memory _data
) internal pure returns (address sender, address receiver, uint256 usdoAmt, bytes32 prevId) {
(sender, receiver, usdoAmt, prevId) = abi.decode(_data, (address, address, uint256, bytes32));
}
/**
* @notice Allows a whitelisted user to perform an instant mint.
* @param underlying The address of the token to mint USDO from.
* @param to The address to mint the USDO to.
* @param amt The supplied amount of the underlying token.
* @param user The end user to mint the USDO to.
*/
function _instantMintInternal(
address underlying,
address from,
address to,
address user,
uint256 amt
) internal returns (uint256, uint256) {
// Convert underlying amount to USDO decimals for comparison
uint256 usdoEquivalent = convertFromUnderlying(underlying, amt);
// if the user has not deposited before, the first deposit amount should be set
// if the user has deposited before, the mint amount should be greater than the mint minimum
// do noted: the first deposit amount will be greater than the mint minimum
if (!_firstDeposit[user]) {
if (usdoEquivalent < _firstDepositAmount)
revert FirstDepositLessThanRequired(usdoEquivalent, _firstDepositAmount);
_firstDeposit[user] = true;
} else {
if (usdoEquivalent < _mintMinimum) revert MintLessThanMinimum(usdoEquivalent, _mintMinimum);
}
(uint256 netAmt, uint256 fee, uint256 usdoAmtCurr, ) = previewMint(underlying, amt);
_checkMintLimit(usdoAmtCurr);
// 2. transfer netAmt to treasury, and fee to feeTo
if (fee > 0) SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(underlying), from, _feeTo, fee);
SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(underlying), from, address(_treasury), netAmt);
_usdo.mint(to, usdoAmtCurr);
return (usdoAmtCurr, fee);
}
/**
* @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[50] private __gap;
}
"
},
"contracts/interfaces/IAssetRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
/**
* @title IAssetRegistry
* @notice Simple interface for managing supported underlying assets
*/
interface IAssetRegistry {
struct AssetConfig {
address asset;
bool isSupported;
address priceFeed; // Optional: IPriceFeed contract for price conversion (like TBILL)
uint256 maxStalePeriod; // Maximum staleness period for this asset's price feed (in seconds)
}
/**
* @notice Add or update an asset configuration
* @param config The asset configuration
*/
function setAssetConfig(AssetConfig calldata config) external;
/**
* @notice Remove an asset from supported assets
* @param asset The asset address
*/
function removeAsset(address asset) external;
/**
* @notice Get asset configuration
* @param asset The asset address
* @return config The asset configuration
*/
function getAssetConfig(address asset) external view returns (AssetConfig memory config);
/**
* @notice Check if asset is supported
* @param asset The asset address
* @return supported True if asset is supported
*/
function isAssetSupported(address asset) external view returns (bool supported);
/**
* @notice Convert asset amount to USDO amount
* @param asset The asset address
* @param assetAmount The asset amount
* @return usdoAmount The equivalent USDO amount
*/
function convertFromUnderlying(address asset, uint256 assetAmount) external view returns (uint256 usdoAmount);
/**
* @notice Convert USDO amount to asset amount
* @param asset The asset address
* @param usdoAmount The USDO amount
* @return assetAmount The equivalent asset amount
*/
function convertToUnderlying(address asset, uint256 usdoAmount) external view returns (uint256 assetAmount);
/**
* @notice Get list of all supported assets
* @return assets Array of supported asset addresses
*/
function getSupportedAssets() external view returns (address[] memory assets);
// Events
event AssetAdded(address indexed asset, AssetConfig config);
event AssetUpdated(address indexed asset, AssetConfig config);
event AssetRemoved(address indexed asset);
event MaxStalePeriodUpdated(uint256 newStalePeriod);
}
"
},
"contracts/interfaces/IRedemption.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.18;
interface IRedemption {
function checkLiquidity() external view returns (uint256, uint256, uint256, uint256, uint256, uint256);
function checkPaused() external view returns (bool);
function redeem(uint256 amount) external returns (uint256 payout, uint256 fee, int256 price);
function redeemFor(address user, uint256 amount) external returns (uint256 payout, uint256 fee, int256 price);
function previewRedeem(uint256 amount) external view returns (uint256 payout, uint256 fee, int256 price);
}
"
},
"contracts/interfaces/ICUSDO.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
interface ICUSDO {
function deposit(uint256 assets, address receiver) external returns (uint256);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256);
function maxDeposit(address receiver) external view returns (uint256);
function maxWithdraw(address owner) external view returns (uint256);
function previewDeposit(uint256 assets) external view returns (uint256);
}
"
},
"contracts/interfaces/IUSDO.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
interface IUSDO {
function bonusMultiplier() external view returns (uint256);
function addBonusMultiplier(uint256 _bonusMultiplierIncrement) external;
function approve(address spender, uint256 amount) external returns (bool);
function mint(address to, uint256 amount) external;
function burn(address from, uint256 amount) external;
function pause() external;
function unpause() external;
function totalSupply() external view returns (uint256);
}
"
},
"contracts/extensions/DoubleQueueModified.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/structs/DoubleEndedQueue.sol)
pragma solidity =0.8.18;
/**
* @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of
* the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and
* FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes {clear}, given that
* the existing queue contents are left in storage.
*
* The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be
* used in storage, and not in memory.
* ```
* DoubleEndedQueue.Bytes32Deque queue;
* ```
*
* _Available since v4.6._
*/
library DoubleQueueModified {
/**
* @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty.
*/
error Empty();
/**
* @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds.
*/
error OutOfBounds();
/**
* @dev Indices are signed integers because the queue can grow in any direction. They are 128 bits so begin and end
* are packed in a single storage slot for efficient access. Since the items are added one at a time we can safely
* assume that these 128-bit indices will not overflow, and use unchecked arithmetic.
*
* Struct members have an underscore prefix indicating that they are "private" and should not be read or written to
* directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and
* lead to unexpected behavior.
*
* Indices are in the range [begin, end) which means the first item is at data[begin] and the last item is at
* data[end - 1].
*/
struct BytesDeque {
int128 _begin;
int128 _end;
mapping(int128 => bytes) _data;
}
/**
* @dev Inserts an item at the end of the queue.
*/
function pushBack(BytesDeque storage deque, bytes memory value) internal {
int128 backIndex = deque._end;
deque._data[backIndex] = value;
unchecked {
deque._end = backIndex + 1;
}
}
/**
* @dev Removes the item at the beginning of the queue and returns it.
*
* Reverts with `Empty` if the queue is empty.
*/
function popFront(BytesDeque storage deque) internal returns (bytes memory value) {
if (empty(deque)) revert Empty();
int128 frontIndex = deque._begin;
value = deque._data[frontIndex];
delete deque._data[frontIndex];
unchecked {
deque._begin = frontIndex + 1;
}
}
/**
* @dev Returns the item at the beginning of the queue.
*
* Reverts with `Empty` if the queue is empty.
*/
function front(BytesDeque storage deque) internal view returns (bytes memory value) {
if (empty(deque)) revert Empty();
int128 frontIndex = deque._begin;
return deque._data[frontIndex];
}
/**
* @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at
* `length(deque) - 1`.
*
* Reverts with `OutOfBounds` if the index is out of bounds.
*/
function at(BytesDeque storage deque, uint256 index) internal view returns (bytes memory value) {
// int256(deque._begin) is a safe upcast
int128 idx = _toInt128(int256(deque._begin) + _toInt256(index));
if (idx >= deque._end) revert OutOfBounds();
return deque._data[idx];
}
function _toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
}
function _toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
/**
* @dev Resets the queue back to being empty.
*
* NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses
* out on potential gas refunds.
*/
function clear(BytesDeque storage deque) internal {
deque._begin = 0;
deque._end = 0;
}
/**
* @dev Returns the number of items in the queue.
*/
function length(BytesDeque storage deque) internal view returns (uint256) {
// The interface preserves the invariant that begin <= end so we assume this will not overflow.
// We also assume there are at most int256.max items in the queue.
unchecked {
return uint256(int256(deque._end) - int256(deque._begin));
}
}
/**
* @dev Returns true if the queue is empty.
*/
function empty(BytesDeque storage deque) internal view returns (bool) {
return deque._end <= deque._begin;
}
}
"
},
"contracts/extensions/USDOMintRedeemLimiterV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
struct USDOMintRedeemLimiterCfg {
// mint
uint256 mintMinimum;
uint256 mintLimit;
uint256 mintDuration;
// redeem
uint256 redeemMinimum;
uint256 redeemLimit;
uint256 redeemDuration;
uint256 firstDepositAmount;
}
/**
* @title USDOMintRedeemLimiter
* @notice contract implementing time-based rate limiting for minting and redeeming.
*/
abstract contract USDOMintRedeemLimiter {
// Total supply cap
uint256 public _RESERVE2; // #previous: _totalSupplyCap
// Mint rate limit
uint256 public _mintMinimum;
uint256 public _mintLimit;
uint256 public _mintDuration;
uint256 public _mintResetTime;
uint256 public _mintedAmount;
// Redeem rate limit
uint256 public _redeemMinimum;
uint256 public _redeemLimit;
uint256 public _redeemDuration;
uint256 public _redeemResetTime;
uint256 public _redeemedAmount;
// First deposit amount
uint256 public _firstDepositAmount;
// Events
event MintMinimumUpdated(uint256 newMinimum);
event MintLimitUpdated(uint256 newLimit);
event MintDurationUpdated(uint256 newDuration);
event RdeemMinimumUpdated(uint256 newMinimum);
event RedeemLimitUpdated(uint256 newLimit);
event RedeemDurationUpdated(uint256 newDuration);
event FirstDepositAmount(uint256 amount);
// Errors
error RedeemLessThanMinimum(uint256 amount, uint256 minimum);
error MintLimitExceeded();
error RedeemLimitExceeded();
/**
* @notice Initializes mint and redeem rate limits.
* @param mintMinimum Min amount allowed to mint in one transaction (in USDO decimals - 18)
* @param mintLimit Max amount allowed to mint in one duration (in USDO decimals - 18)
* @param mintDuration Reset duration for minting (seconds)
* @param redeemMinimum Min amount allowed to redeem in one transaction (in USDO decimals - 18)
* @param redeemLimit Max amount allowed to redeem in one duration (in USDO decimals - 18)
* @param redeemDuration Reset duration for redeeming (seconds)
* @param firstDepositAmount The first deposit amount (in USDO decimals - 18)
*/
function __USDOMintRedeemLimiter_init(
uint256 mintMinimum,
uint256 mintLimit,
uint256 mintDuration,
uint256 redeemMinimum,
uint256 redeemLimit,
uint256 redeemDuration,
uint256 firstDepositAmount
) internal {
//mint
_mintMinimum = mintMinimum;
_mintLimit = mintLimit;
_mintDuration = mintDuration;
//redeem
_redeemMinimum = redeemMinimum;
_redeemLimit = redeemLimit;
_redeemDuration = redeemDuration;
_firstDepositAmount = firstDepositAmount;
_mintResetTime = block.timestamp;
_redeemResetTime = block.timestamp;
}
/*//////////////////////////////////////////////////////////////
Mint Limit Functions
//////////////////////////////////////////////////////////////*/
/**
* @dev Ensures mint amount doesn't exceed the rate limit.
* @param amount Amount to mint.
*/
function _checkMintLimit(uint256 amount) internal {
if (block.timestamp >= _mintResetTime + _mintDuration) {
_mintedAmount = 0;
_mintResetTime = block.timestamp;
}
if (_mintedAmount + amount > _mintLimit) revert MintLimitExceeded();
_mintedAmount += amount;
}
/**
* @dev Updates the mint minimum.
* @dev Amount should be in USDO decimals (18) for accurate comparison across different underlying assets
* @param mintMinimum New mint minimum in USDO equivalent.
*/
function _setMintMinimum(uint256 mintMinimum) internal {
_mintMinimum = mintMinimum;
emit MintMinimumUpdated(mintMinimum);
}
/**
* @dev Updates the mint limit.
* @param mintLimit New mint limit.
*/
function _setMintLimit(uint256 mintLimit) internal {
_mintLimit = mintLimit;
emit MintLimitUpdated(mintLimit);
}
/**
* @dev Updates the mint duration.
* @param mintDuration New mint duration (seconds).
*/
function _setMintDuration(uint256 mintDuration) internal {
_mintDuration = mintDuration;
emit MintDurationUpdated(mintDuration);
}
/*//////////////////////////////////////////////////////////////
Redeem Limit Functions
//////////////////////////////////////////////////////////////*/
/**
* @dev Ensures redeem amount doesn't exceed the rate limit.
* @param amount Amount to redeem.
*/
function _checkRedeemLimit(uint256 amount) internal {
if (amount < _redeemMinimum) revert RedeemLessThanMinimum(amount, _redeemMinimum);
if (block.timestamp >= _redeemResetTime + _redeemDuration) {
_redeemedAmount = 0;
_redeemResetTime = block.timestamp;
}
if (_redeemedAmount + amount > _redeemLimit) revert RedeemLimitExceeded();
_redeemedAmount += amount;
}
/**
* @dev Updates the redeem minimum.
* @param redeemMinimum New redeem minimum.
*/
function _setRedeemMinimum(uint256 redeemMinimum) internal {
_redeemMinimum = redeemMinimum;
emit RdeemMinimumUpdated(redeemMinimum);
}
/**
* @dev Updates the redeem limit.
* @param redeemLimit New redeem limit.
*/
function _setRedeemLimit(uint256 redeemLimit) internal {
_redeemLimit = redeemLimit;
emit RedeemLimitUpdated(redeemLimit);
}
/**
* @dev Updates the redeem duration.
* @param redeemDuration New redeem duration (seconds).
*/
function _setRedeemDuration(uint256 redeemDuration) internal {
_redeemDuration = redeemDuration;
emit RedeemDurationUpdated(redeemDuration);
}
/// @notice Set the first deposit amount
/// @dev Amount should be in USDO decimals (18) for accurate comparison across different underlying assets
/// @param amount The first deposit amount in USDO equivalent
function _setFirstDepositAmount(uint256 amount) internal {
_firstDepositAmount = amount;
emit FirstDepositAmount(amount);
}
uint256[10] private __gap;
}
"
},
"contracts/extensions/USDOExpressPausable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity =0.8.18;
import "@openzeppelin/contracts/utils/Context.sol";
abstract contract USDOExpressPausable {
event PausedMint(address account);
event PausedRedeem(address account);
event UnpausedMint(address account);
event UnpausedRedeem(address account);
bool private _pausedMint;
bool private _pausedRedeem;
/*//////////////////////////////////////////////////////////////
Paused Mint
//////////////////////////////////////////////////////////////*/
modifier whenNotPausedMint() {
require(!pausedMint(), "Pausable: Mint paused");
_;
}
modifier whenPausedMint() {
require(pausedMint(), "Pausable: Mint not paused");
_;
}
function pausedMint() public view virtual returns (bool) {
return _pausedMint;
}
function _pauseMint() internal virtual whenNotPausedMint {
_pausedMint = true;
emit PausedMint(msg.sender);
}
function _unpauseMint() internal virtual whenPausedMint {
_pausedMint = false;
emit UnpausedMint(msg.sender);
}
/*//////////////////////////////////////////////////////////////
Paused Redeem
//////////////////////////////////////////////////////////////*/
modifier whenNotPausedRedeem() {
require(!pausedRedeem(), "Pausable: Redeem paused");
_;
}
modifier whenPausedRedeem() {
require(pausedRedeem(), "Pausable: Redeem not paused");
_;
}
function pausedRedeem() public view virtual returns (bool) {
return _pausedRedeem;
}
function _pauseRedeem() internal virtual whenNotPausedRedeem {
_pausedRedeem = true;
emit PausedRedeem(msg.sender);
}
function _unpauseRedeem() internal virtual whenPausedRedeem {
_pausedRedeem = false;
emit UnpausedRedeem(msg.sender);
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library MathUpgradeable {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @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 up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 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; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
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.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 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.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
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 (rounding == Rounding.Up && 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 down.
*
* 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 + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
import "../extensions/IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
using AddressUpgradeable for address;
/**
* @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(IERC20Upgradeable token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from
Submitted on: 2025-10-24 19:47:18
Comments
Log in to comment.
No comments yet.