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/withdraws/Ethena.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import { IsUSDe, sUSDe, USDe } from "../interfaces/IEthena.sol";
import { ClonedCoolDownHolder } from "./ClonedCoolDownHolder.sol";
import { AbstractWithdrawRequestManager } from "./AbstractWithdrawRequestManager.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import { ADDRESS_REGISTRY } from "../utils/Constants.sol";
contract EthenaCooldownHolder is ClonedCoolDownHolder {
uint256 public instantRedeemBalance;
uint256 public expectedRedeemBalance;
constructor(address _manager) ClonedCoolDownHolder(_manager) { }
/// @notice There is no way to stop a cool down
function _stopCooldown() internal pure override {
revert();
}
function _startCooldown(uint256 cooldownBalance) internal override {
uint24 duration = sUSDe.cooldownDuration();
if (duration == 0) {
// If the cooldown duration is set to zero, can redeem immediately
instantRedeemBalance = sUSDe.redeem(cooldownBalance, address(this), address(this));
} else {
// If we execute a second cooldown while one exists, the cooldown end
// will be pushed further out. This holder should only ever have one
// cooldown ever.
require(sUSDe.cooldowns(address(this)).cooldownEnd == 0);
expectedRedeemBalance = sUSDe.cooldownShares(cooldownBalance);
}
}
function _finalizeCooldown() internal override returns (uint256 tokensClaimed, bool finalized) {
uint24 duration = sUSDe.cooldownDuration();
IsUSDe.UserCooldown memory userCooldown = sUSDe.cooldowns(address(this));
if (block.timestamp < userCooldown.cooldownEnd && 0 < duration) {
// Cooldown has not completed, return a false for finalized
return (0, false);
}
uint256 balanceBefore = USDe.balanceOf(address(this));
// If a cooldown has been initiated, need to call unstake to complete it. If
// duration was set to zero then the USDe will be on this contract already.
if (0 < userCooldown.cooldownEnd) sUSDe.unstake(address(this));
uint256 balanceAfter = USDe.balanceOf(address(this));
// USDe is immutable. It cannot have a transfer tax and it is ERC20 compliant
// so we do not need to use the additional protections here.
tokensClaimed = balanceAfter - balanceBefore + instantRedeemBalance;
USDe.transfer(manager, tokensClaimed); // forge-lint: disable-line
finalized = true;
}
}
contract EthenaWithdrawRequestManager is AbstractWithdrawRequestManager {
address public HOLDER_IMPLEMENTATION;
constructor() AbstractWithdrawRequestManager(address(USDe), address(sUSDe), address(USDe)) { }
function redeployHolder() external {
require(msg.sender == ADDRESS_REGISTRY.upgradeAdmin());
HOLDER_IMPLEMENTATION = address(new EthenaCooldownHolder(address(this)));
}
function _initialize(bytes calldata /* data */ ) internal override {
HOLDER_IMPLEMENTATION = address(new EthenaCooldownHolder(address(this)));
}
function _stakeTokens(uint256 usdeAmount, bytes memory /* stakeData */ ) internal override {
USDe.approve(address(sUSDe), usdeAmount);
sUSDe.deposit(usdeAmount, address(this));
}
function _initiateWithdrawImpl(
address, /* account */
uint256 balanceToTransfer,
bytes calldata, /* data */
address /* forceWithdrawFrom */
)
internal
override
returns (uint256 requestId)
{
EthenaCooldownHolder holder = EthenaCooldownHolder(Clones.clone(HOLDER_IMPLEMENTATION));
sUSDe.transfer(address(holder), balanceToTransfer); // forge-lint: disable-line
holder.startCooldown(balanceToTransfer);
return uint256(uint160(address(holder)));
}
function _finalizeWithdrawImpl(
address, /* account */
uint256 requestId
)
internal
override
returns (uint256 tokensClaimed)
{
EthenaCooldownHolder holder = EthenaCooldownHolder(address(uint160(requestId)));
bool finalized;
(tokensClaimed, finalized) = holder.finalizeCooldown();
require(finalized);
}
function getKnownWithdrawTokenAmount(uint256 requestId)
public
view
override
returns (bool hasKnownAmount, uint256 amount)
{
EthenaCooldownHolder holder = EthenaCooldownHolder(address(uint160(requestId)));
hasKnownAmount = true;
amount = holder.expectedRedeemBalance() + holder.instantRedeemBalance();
}
function canFinalizeWithdrawRequest(uint256 requestId) public view override returns (bool) {
uint24 duration = sUSDe.cooldownDuration();
address holder = address(uint160(requestId));
// This valuation is the amount of USDe the account will receive at cooldown, once
// a cooldown is initiated the account is no longer receiving sUSDe yield. This balance
// of USDe is transferred to a Silo contract and guaranteed to be available once the
// cooldown has passed.
IsUSDe.UserCooldown memory userCooldown = sUSDe.cooldowns(holder);
return (userCooldown.cooldownEnd < block.timestamp || 0 == duration);
}
}
"
},
"src/interfaces/IEthena.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Mainnet Ethena contract addresses
IsUSDe constant sUSDe = IsUSDe(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497);
ERC20 constant USDe = ERC20(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3);
// Dai and sDAI are required for trading out of sUSDe
ERC20 constant DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
IERC4626 constant sDAI = IERC4626(0x83F20F44975D03b1b09e64809B757c47f942BEeA);
interface IsUSDe is IERC4626 {
struct UserCooldown {
uint104 cooldownEnd;
uint152 underlyingAmount;
}
function cooldownDuration() external view returns (uint24);
function cooldowns(address account) external view returns (UserCooldown memory);
function cooldownShares(uint256 shares) external returns (uint256 assets);
function unstake(address receiver) external;
function setCooldownDuration(uint24 duration) external;
function owner() external view returns (address);
}
"
},
"src/withdraws/ClonedCoolDownHolder.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @notice Used for withdraws where only one cooldown period can exist per address,
* this contract will receive the staked token and initiate a cooldown
*/
abstract contract ClonedCoolDownHolder {
using SafeERC20 for ERC20;
/// @notice The manager contract that is responsible for managing the cooldown period.
address public immutable manager;
constructor(address _manager) {
manager = _manager;
}
modifier onlyManager() {
require(msg.sender == manager);
_;
}
/// @notice If anything ever goes wrong, allows the manager to recover lost tokens.
function rescueTokens(ERC20 token, address receiver, uint256 amount) external onlyManager {
token.safeTransfer(receiver, amount);
}
// External methods with authentication
function startCooldown(uint256 cooldownBalance) external onlyManager {
_startCooldown(cooldownBalance);
}
function stopCooldown() external onlyManager {
_stopCooldown();
}
function finalizeCooldown() external onlyManager returns (uint256 tokensWithdrawn, bool finalized) {
return _finalizeCooldown();
}
// Internal implementation methods
function _startCooldown(uint256 cooldownBalance) internal virtual;
function _stopCooldown() internal virtual;
function _finalizeCooldown() internal virtual returns (uint256 tokensWithdrawn, bool finalized);
}
"
},
"src/withdraws/AbstractWithdrawRequestManager.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import {
IWithdrawRequestManager,
WithdrawRequest,
TokenizedWithdrawRequest,
StakingTradeParams
} from "../interfaces/IWithdrawRequestManager.sol";
import { Initializable } from "../proxy/Initializable.sol";
import { ClonedCoolDownHolder } from "./ClonedCoolDownHolder.sol";
import {
Unauthorized,
ExistingWithdrawRequest,
NoWithdrawRequest,
InvalidWithdrawRequestTokenization
} from "../interfaces/Errors.sol";
import { TypeConvert } from "../utils/TypeConvert.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { ADDRESS_REGISTRY, DEFAULT_PRECISION } from "../utils/Constants.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Trade, TradeType, TRADING_MODULE, nProxy, ITradingModule } from "../interfaces/ITradingModule.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
/**
* Library to handle potentially illiquid withdraw requests of staking tokens where there
* is some indeterminate lock up time before tokens can be redeemed. Examples would be withdraws
* of staked or restaked ETH, tokens like sUSDe or stkAave which have cooldown periods before they
* can be withdrawn.
*
* Primarily, this library tracks the withdraw request and an associated identifier for the withdraw
* request. It also allows for the withdraw request to be "tokenized" so that shares of the withdraw
* request can be liquidated.
*/
abstract contract AbstractWithdrawRequestManager is IWithdrawRequestManager, Initializable, ReentrancyGuardTransient {
using SafeERC20 for ERC20;
using TypeConvert for uint256;
/// @inheritdoc IWithdrawRequestManager
address public immutable override YIELD_TOKEN;
/// @inheritdoc IWithdrawRequestManager
address public immutable override WITHDRAW_TOKEN;
/// @inheritdoc IWithdrawRequestManager
address public immutable override STAKING_TOKEN;
mapping(address vault => bool isApproved) public override isApprovedVault;
mapping(address vault => mapping(address account => WithdrawRequest)) private s_accountWithdrawRequest;
mapping(uint256 requestId => TokenizedWithdrawRequest tokenizedWithdrawRequest) private s_tokenizedWithdrawRequest;
constructor(address _withdrawToken, address _yieldToken, address _stakingToken) Initializable() {
WITHDRAW_TOKEN = _withdrawToken;
YIELD_TOKEN = _yieldToken;
STAKING_TOKEN = _stakingToken;
}
modifier onlyOwner() {
if (msg.sender != ADDRESS_REGISTRY.upgradeAdmin()) revert Unauthorized(msg.sender);
_;
}
/// @dev Ensures that only approved vaults can initiate withdraw requests.
modifier onlyApprovedVault() {
if (!isApprovedVault[msg.sender]) revert Unauthorized(msg.sender);
_;
}
/// @inheritdoc IWithdrawRequestManager
function getWithdrawRequest(
address vault,
address account
)
public
view
override
returns (WithdrawRequest memory w, TokenizedWithdrawRequest memory s)
{
w = s_accountWithdrawRequest[vault][account];
s = s_tokenizedWithdrawRequest[w.requestId];
}
/// @inheritdoc IWithdrawRequestManager
function isPendingWithdrawRequest(address vault, address account) public view override returns (bool) {
return s_accountWithdrawRequest[vault][account].requestId != 0;
}
/// @inheritdoc IWithdrawRequestManager
function setApprovedVault(address vault, bool isApproved) external override onlyOwner {
isApprovedVault[vault] = isApproved;
emit ApprovedVault(vault, isApproved);
}
/// @inheritdoc IWithdrawRequestManager
function stakeTokens(
address depositToken,
uint256 amount,
bytes calldata data
)
external
override
onlyApprovedVault
nonReentrant
returns (uint256 yieldTokensMinted)
{
uint256 initialYieldTokenBalance = ERC20(YIELD_TOKEN).balanceOf(address(this));
ERC20(depositToken).safeTransferFrom(msg.sender, address(this), amount);
(uint256 stakeTokenAmount, bytes memory stakeData) = _preStakingTrade(depositToken, amount, data);
_stakeTokens(stakeTokenAmount, stakeData);
yieldTokensMinted = ERC20(YIELD_TOKEN).balanceOf(address(this)) - initialYieldTokenBalance;
ERC20(YIELD_TOKEN).safeTransfer(msg.sender, yieldTokensMinted);
// Emits the amount of staking tokens for the yield token at this point in time.
emit ITradingModule.TradeExecuted(STAKING_TOKEN, YIELD_TOKEN, stakeTokenAmount, yieldTokensMinted);
}
/// @inheritdoc IWithdrawRequestManager
function initiateWithdraw(
address account,
uint256 yieldTokenAmount,
uint256 sharesAmount,
bytes calldata data,
address forceWithdrawFrom
)
external
override
onlyApprovedVault
nonReentrant
returns (uint256 requestId)
{
WithdrawRequest storage accountWithdraw = s_accountWithdrawRequest[msg.sender][account];
if (accountWithdraw.requestId != 0) {
revert ExistingWithdrawRequest(msg.sender, account, accountWithdraw.requestId);
}
// Receive the requested amount of yield tokens from the approved vault.
ERC20(YIELD_TOKEN).safeTransferFrom(msg.sender, address(this), yieldTokenAmount);
requestId = _initiateWithdrawImpl(account, yieldTokenAmount, data, forceWithdrawFrom);
accountWithdraw.requestId = requestId;
accountWithdraw.yieldTokenAmount = yieldTokenAmount.toUint120();
accountWithdraw.sharesAmount = sharesAmount.toUint120();
s_tokenizedWithdrawRequest[requestId] = TokenizedWithdrawRequest({
totalYieldTokenAmount: yieldTokenAmount.toUint120(),
totalWithdraw: 0,
finalized: false
});
emit InitiateWithdrawRequest(account, msg.sender, yieldTokenAmount, sharesAmount, requestId);
}
/// @inheritdoc IWithdrawRequestManager
function finalizeAndRedeemWithdrawRequest(
address account,
uint256 withdrawYieldTokenAmount,
uint256 sharesToBurn
)
external
override
onlyApprovedVault
nonReentrant
returns (uint256 tokensWithdrawn)
{
WithdrawRequest storage s_withdraw = s_accountWithdrawRequest[msg.sender][account];
if (s_withdraw.requestId == 0) return 0;
tokensWithdrawn = _finalizeWithdraw(account, msg.sender, s_withdraw);
// Allows for partial withdrawal of yield tokens
uint256 requestId = s_withdraw.requestId;
bool isCleared = false;
if (withdrawYieldTokenAmount < s_withdraw.yieldTokenAmount) {
tokensWithdrawn = tokensWithdrawn * withdrawYieldTokenAmount / s_withdraw.yieldTokenAmount;
s_withdraw.sharesAmount -= sharesToBurn.toUint120();
s_withdraw.yieldTokenAmount -= withdrawYieldTokenAmount.toUint120();
} else {
require(s_withdraw.yieldTokenAmount == withdrawYieldTokenAmount);
delete s_accountWithdrawRequest[msg.sender][account];
isCleared = true;
}
emit WithdrawRequestRedeemed(msg.sender, account, requestId, withdrawYieldTokenAmount, sharesToBurn, isCleared);
ERC20(WITHDRAW_TOKEN).safeTransfer(msg.sender, tokensWithdrawn);
}
/// @inheritdoc IWithdrawRequestManager
function finalizeRequestManual(
address vault,
address account
)
external
override
nonReentrant
returns (uint256 tokensWithdrawn)
{
WithdrawRequest storage s_withdraw = s_accountWithdrawRequest[vault][account];
if (s_withdraw.requestId == 0) revert NoWithdrawRequest(vault, account);
// Do not transfer any tokens off of this method here. Withdrawn tokens will be held in the
// tokenized withdraw request until the vault calls this contract to withdraw the tokens.
tokensWithdrawn = _finalizeWithdraw(account, vault, s_withdraw);
}
/// @inheritdoc IWithdrawRequestManager
function tokenizeWithdrawRequest(
address _from,
address _to,
uint256 sharesAmount
)
external
override
onlyApprovedVault
nonReentrant
returns (bool didTokenize)
{
if (_from == _to) revert();
WithdrawRequest storage s_withdraw = s_accountWithdrawRequest[msg.sender][_from];
uint256 requestId = s_withdraw.requestId;
if (requestId == 0 || sharesAmount == 0) return false;
// Ensure that no withdraw request gets overridden, the _to account always receives their withdraw
// request in the account withdraw slot. All storage is updated prior to changes to the `w` storage
// variable below.
WithdrawRequest storage toWithdraw = s_accountWithdrawRequest[msg.sender][_to];
if (toWithdraw.requestId != 0 && toWithdraw.requestId != requestId) {
revert ExistingWithdrawRequest(msg.sender, _to, toWithdraw.requestId);
}
toWithdraw.requestId = requestId;
if (s_withdraw.sharesAmount < sharesAmount) {
// This should never occur given the checks below.
revert InvalidWithdrawRequestTokenization();
} else if (s_withdraw.sharesAmount == sharesAmount) {
// If the resulting vault shares is zero, then delete the request. The _from account's
// withdraw request is fully transferred to _to. In this case, the _to account receives
// the full amount of the _from account's withdraw request.
toWithdraw.yieldTokenAmount = toWithdraw.yieldTokenAmount + s_withdraw.yieldTokenAmount;
toWithdraw.sharesAmount = toWithdraw.sharesAmount + s_withdraw.sharesAmount;
delete s_accountWithdrawRequest[msg.sender][_from];
} else {
// In this case, the amount of yield tokens is transferred from one account to the other.
uint256 yieldTokenAmount = s_withdraw.yieldTokenAmount * sharesAmount / s_withdraw.sharesAmount;
toWithdraw.yieldTokenAmount = (toWithdraw.yieldTokenAmount + yieldTokenAmount).toUint120();
toWithdraw.sharesAmount = (toWithdraw.sharesAmount + sharesAmount).toUint120();
s_withdraw.yieldTokenAmount = (s_withdraw.yieldTokenAmount - yieldTokenAmount).toUint120();
s_withdraw.sharesAmount = (s_withdraw.sharesAmount - sharesAmount).toUint120();
}
emit WithdrawRequestTokenized(_from, _to, msg.sender, requestId, sharesAmount);
return true;
}
/// @inheritdoc IWithdrawRequestManager
function rescueTokens(
address cooldownHolder,
address token,
address receiver,
uint256 amount
)
external
override
onlyOwner
{
ClonedCoolDownHolder(cooldownHolder).rescueTokens(ERC20(token), receiver, amount);
}
/// @notice Finalizes a withdraw request and updates the account required to determine how many
/// tokens the account has a claim over.
function _finalizeWithdraw(
address account,
address vault,
WithdrawRequest memory w
)
internal
returns (uint256 tokensWithdrawn)
{
TokenizedWithdrawRequest storage s = s_tokenizedWithdrawRequest[w.requestId];
// If the tokenized request was already finalized in a different transaction
// then return the values here and we can short circuit the withdraw impl
if (s.finalized) {
return uint256(s.totalWithdraw) * uint256(w.yieldTokenAmount) / uint256(s.totalYieldTokenAmount);
}
// These values are the total tokens claimed from the withdraw request, does not
// account for potential tokenization.
tokensWithdrawn = _finalizeWithdrawImpl(account, w.requestId);
s.totalWithdraw = tokensWithdrawn.toUint120();
// Safety check to ensure that we do not override a finalized tokenized withdraw request
require(s.finalized == false);
s.finalized = true;
tokensWithdrawn = uint256(s.totalWithdraw) * uint256(w.yieldTokenAmount) / uint256(s.totalYieldTokenAmount);
emit WithdrawRequestFinalized(vault, account, w.requestId, s.totalWithdraw);
}
/// @notice Required implementation to begin the withdraw request
/// @return requestId some identifier of the withdraw request
function _initiateWithdrawImpl(
address account,
uint256 yieldTokenAmount,
bytes calldata data,
address forceWithdrawFrom
)
internal
virtual
returns (uint256 requestId);
/// @notice Required implementation to finalize the withdraw
/// @dev Must revert if the withdraw request is not finalized
/// @return tokensWithdrawn total tokens claimed as a result of the withdraw, does not
/// necessarily represent the tokens that go to the account if the request has been
/// tokenized due to liquidation
function _finalizeWithdrawImpl(
address account,
uint256 requestId
)
internal
virtual
returns (uint256 tokensWithdrawn);
/// @notice Required implementation to stake the deposit token to the yield token
function _stakeTokens(uint256 amount, bytes memory stakeData) internal virtual;
/// @dev Allows for the deposit token to be traded into the staking token prior to staking, i.e.
/// enables USDC to USDe trades before staking into sUSDe.
function _preStakingTrade(
address depositToken,
uint256 depositAmount,
bytes calldata data
)
internal
returns (uint256 amountBought, bytes memory stakeData)
{
if (depositToken == STAKING_TOKEN) {
amountBought = depositAmount;
stakeData = data;
} else {
StakingTradeParams memory params = abi.decode(data, (StakingTradeParams));
stakeData = params.stakeData;
( /* */ , amountBought) = _executeTrade(
Trade({
tradeType: TradeType.EXACT_IN_SINGLE,
sellToken: depositToken,
buyToken: STAKING_TOKEN,
amount: depositAmount,
exchangeData: params.exchangeData,
limit: params.minPurchaseAmount,
deadline: block.timestamp
}),
params.dexId
);
}
}
function _executeTrade(
Trade memory trade,
uint16 dexId
)
internal
returns (uint256 amountSold, uint256 amountBought)
{
(bool success, bytes memory result) = nProxy(payable(address(TRADING_MODULE))).getImplementation().delegatecall(
abi.encodeWithSelector(TRADING_MODULE.executeTrade.selector, dexId, trade)
);
if (!success) {
assembly {
// Copy the return data to memory
returndatacopy(0, 0, returndatasize())
// Revert with the return data
revert(0, returndatasize())
}
}
(amountSold, amountBought) = abi.decode(result, (uint256, uint256));
}
function getKnownWithdrawTokenAmount(uint256 /* requestId */ )
public
view
virtual
override
returns (bool hasKnownAmount, uint256 amount)
{
return (false, 0);
}
/// @inheritdoc IWithdrawRequestManager
function getWithdrawRequestValue(
address vault,
address account,
address asset,
uint256 shares
)
external
view
override
returns (bool hasRequest, uint256 valueInAsset)
{
WithdrawRequest memory w = s_accountWithdrawRequest[vault][account];
if (w.requestId == 0) return (false, 0);
TokenizedWithdrawRequest memory s = s_tokenizedWithdrawRequest[w.requestId];
(bool hasKnownAmount, uint256 knownAmount) = getKnownWithdrawTokenAmount(w.requestId);
int256 tokenRate;
uint256 tokenAmount;
uint256 tokenDecimals;
uint256 assetDecimals = TokenUtils.getDecimals(asset);
if (s.finalized || hasKnownAmount) {
// If finalized the withdraw request is locked to the tokens withdrawn
(tokenRate, /* */ ) = TRADING_MODULE.getOraclePrice(WITHDRAW_TOKEN, asset);
tokenDecimals = TokenUtils.getDecimals(WITHDRAW_TOKEN);
uint256 totalWithdraw = s.finalized ? uint256(s.totalWithdraw) : knownAmount;
tokenAmount = (uint256(w.yieldTokenAmount) * totalWithdraw) / uint256(s.totalYieldTokenAmount);
} else {
// Otherwise we use the yield token rate
(tokenRate, /* */ ) = TRADING_MODULE.getOraclePrice(YIELD_TOKEN, asset);
tokenDecimals = TokenUtils.getDecimals(YIELD_TOKEN);
tokenAmount = w.yieldTokenAmount;
}
// The trading module always returns a positive rate in 18 decimals so we can safely
// cast to uint256
uint256 totalValue =
(uint256(tokenRate) * tokenAmount * (10 ** assetDecimals)) / ((10 ** tokenDecimals) * DEFAULT_PRECISION);
// NOTE: returns the normalized value given the shares input
return (true, totalValue * shares / w.sharesAmount);
}
/// @inheritdoc IWithdrawRequestManager
function getExchangeRate() public view virtual override returns (uint256) {
// Covers the base case where the yield token is an ERC4626 vault and returns
// the exchange rate of the yield token back to the staking token.
return IERC4626(YIELD_TOKEN).convertToAssets(10 ** ERC20(YIELD_TOKEN).decimals());
}
}
"
},
"node_modules/@openzeppelin/contracts/proxy/Clones.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}
"
},
"src/utils/Constants.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import { WETH9 } from "../interfaces/IWETH.sol";
import { AddressRegistry } from "../proxy/AddressRegistry.sol";
address constant ETH_ADDRESS = address(0);
address constant ALT_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 constant DEFAULT_PRECISION = 1e18;
uint256 constant DEFAULT_DECIMALS = 18;
uint256 constant SHARE_PRECISION = 1e24;
uint256 constant VIRTUAL_SHARES = 1e6;
uint256 constant COOLDOWN_PERIOD = 5 minutes;
uint256 constant YEAR = 365 days;
// Will move these to a deployment file when we go to multiple chains
uint256 constant CHAIN_ID_MAINNET = 1;
WETH9 constant WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
AddressRegistry constant ADDRESS_REGISTRY = AddressRegistry(0xe335d314BD4eF7DD44F103dC124FEFb7Ce63eC95);
"
},
"node_modules/@openzeppelin/contracts/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 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 {
/**
* @dev An operation with an ERC-20 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 successfu
Submitted on: 2025-09-22 20:58:44
Comments
Log in to comment.
No comments yet.