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/collectors/defi/rstETHPlusTestCollector.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../../../src/libraries/TransferLibrary.sol";
import "../../../src/oracles/OracleHelper.sol";
import "../../../src/vaults/Vault.sol";
import {Constants} from "../../ethereum/Constants.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import "forge-std/console2.sol";
interface IWSTETH {
function getStETHByWstETH(uint256) external view returns (uint256);
}
contract rstETHPlusTestCollector {
Vault public constant vault = Vault(payable(0x576cf925B2F58328a4Bd5A95A74541a90976e1B4));
OracleHelper public constant helper = OracleHelper(0x000000005F543c38d5ea6D0bF10A50974Eb55E35);
function getPricesD18() external view returns (IOracle.Report[] memory reports) {
uint256 totalAssets = getTotalAssets(address(vault)) + getTotalAssets(vault.subvaultAt(0));
console2.log(
"Total assets:",
totalAssets
);
OracleHelper.AssetPrice[] memory prices = new OracleHelper.AssetPrice[](4);
prices[0].asset = Constants.RSTETH;
prices[0].priceD18 =
uint224(IWSTETH(Constants.WSTETH).getStETHByWstETH(IERC4626(Constants.RSTETH).convertToAssets(1 ether)));
prices[1].asset = Constants.WSTETH;
prices[1].priceD18 = uint224(IWSTETH(Constants.WSTETH).getStETHByWstETH(1 ether));
prices[2].asset = Constants.WETH;
prices[2].priceD18 = 1 ether;
prices[3].asset = Constants.ETH;
uint256[] memory prices_ = helper.getPricesD18(vault, totalAssets, prices);
reports = new IOracle.Report[](4);
for (uint256 i = 0; i < 4; i++) {
reports[i].asset = prices[3 - i].asset;
reports[i].priceD18 = uint224(prices_[3 - i]);
}
}
function getTotalAssets(address v) public view returns (uint256) {
return TransferLibrary.balanceOf(Constants.ETH, v) + TransferLibrary.balanceOf(Constants.WETH, v)
+ IWSTETH(Constants.WSTETH).getStETHByWstETH(
TransferLibrary.balanceOf(Constants.WSTETH, v)
+ IERC4626(Constants.RSTETH).convertToAssets(TransferLibrary.balanceOf(Constants.RSTETH, v))
);
}
}
"
},
"src/libraries/TransferLibrary.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/// @title TransferLibrary
/// @notice Library for unified handling of native ETH and ERC20 asset transfers.
/// @dev Provides safe and abstracted methods for sending and receiving both ETH and ERC20 tokens.
///
/// # ETH Convention
/// Uses the constant `ETH = 0xEeee...EeE` to distinguish native ETH from ERC20 tokens.
library TransferLibrary {
using SafeERC20 for IERC20;
/// @notice Error thrown when `msg.value` does not match expected ETH amount
error InvalidValue();
/// @dev Placeholder address used to represent native ETH transfers
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice Safely sends assets (ETH or ERC20) to a recipient
/// @param asset Address of the asset to send (use `ETH` constant for native ETH)
/// @param to Recipient address
/// @param assets Amount of assets to send
/// @dev Uses `Address.sendValue` for ETH and `safeTransfer` for ERC20
function sendAssets(address asset, address to, uint256 assets) internal {
if (asset == ETH) {
Address.sendValue(payable(to), assets);
} else {
IERC20(asset).safeTransfer(to, assets);
}
}
/// @notice Safely receives assets (ETH or ERC20) from a sender
/// @param asset Address of the asset to receive (use `ETH` constant for native ETH)
/// @param from Sender address (only used for ERC20)
/// @param assets Amount of assets expected to receive
/// @dev Reverts if `msg.value` is incorrect for ETH or uses `safeTransferFrom` for ERC20
function receiveAssets(address asset, address from, uint256 assets) internal {
if (asset == ETH) {
if (msg.value != assets) {
revert InvalidValue();
}
} else {
IERC20(asset).safeTransferFrom(from, address(this), assets);
}
}
/// @notice Returns the balance of an account for a given asset
/// @param asset Address of the asset to check the balance of (use `ETH` constant for native ETH)
/// @param account Address of the account to check the balance of
/// @return Balance of the account for the given asset
function balanceOf(address asset, address account) internal view returns (uint256) {
if (asset == ETH) {
return account.balance;
} else {
return IERC20(asset).balanceOf(account);
}
}
}
"
},
"src/oracles/OracleHelper.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../vaults/Vault.sol";
contract OracleHelper {
struct AssetPrice {
address asset;
/**
* Price of the asset expressed via the base asset.
* If the price is 0, it means that the asset is the base asset, then for other assets:
* - If priceD18 = 1e18, it means that 1 asset = 1 base asset
* - If priceD18 = 0.5e18, it means that 1 asset = 0.5 base asset
* - If priceD18 = 2e18, it means that 1 asset = 2 base assets
*/
uint256 priceD18;
}
/**
* Calculates the prices of the vault's assets which will be reported to the oracle.
* @param vault Vault to calculate the prices for.
* @param totalAssets Total assets in the vault expressed via the base asset (TVL denominated in the base asset).
* @param assetPrices Prices of the assets.
*/
function getPricesD18(Vault vault, uint256 totalAssets, AssetPrice[] calldata assetPrices)
external
view
returns (uint256[] memory pricesD18)
{
// Step 1. Find the base asset index.
uint256 baseAssetIndex = type(uint256).max;
pricesD18 = new uint256[](assetPrices.length);
for (uint256 i = 0; i < assetPrices.length; i++) {
if (0 < i && assetPrices[i].asset <= assetPrices[i - 1].asset) {
revert("OracleHelper: invalid asset order");
}
if (assetPrices[i].priceD18 == 0) {
if (baseAssetIndex < type(uint256).max) {
revert("OracleHelper: multiple base assets");
}
baseAssetIndex = i;
}
}
// Step 2. Process withdrawal queues.
// Calculate total demand assets (expressed via the base asset) and unprocessed shares.
IFeeManager feeManager = vault.feeManager();
{
uint256 queueAssets = vault.getAssetCount();
for (uint256 i = 0; i < queueAssets; i++) {
address queueAsset = vault.assetAt(i);
uint256 queueCount = vault.getQueueCount(queueAsset);
AssetPrice calldata assetPrice = assetPrices[0];
for (uint256 j = 0; j < assetPrices.length; j++) {
if (assetPrices[j].asset == queueAsset) {
assetPrice = assetPrices[j];
break;
}
}
if (assetPrice.asset != queueAsset) {
revert("OracleHelper: asset not found");
}
for (uint256 j = 0; j < queueCount; j++) {
address queue = vault.queueAt(queueAsset, j);
if (vault.isDepositQueue(queue) || IQueue(queue).canBeRemoved()) {
continue;
}
(,, uint256 demand_,) = IRedeemQueue(queue).getState();
if (assetPrice.priceD18 == 0) {
totalAssets -= demand_;
} else {
totalAssets -= Math.mulDiv(demand_, assetPrice.priceD18, 1 ether);
}
}
}
}
// Step 3. Calculate the price of the base asset.
pricesD18[baseAssetIndex] =
_calculateBasePriceD18(vault, feeManager, assetPrices[baseAssetIndex].asset, totalAssets);
// Step 4. Calculate the price of the other assets based on the base asset.
for (uint256 i = 0; i < assetPrices.length; i++) {
if (i != baseAssetIndex) {
pricesD18[i] = Math.mulDiv(pricesD18[baseAssetIndex], assetPrices[i].priceD18, 1 ether);
}
if (pricesD18[i] > type(uint224).max || pricesD18[i] == 0) {
revert("OracleHelper: invalid price");
}
}
}
function _calculateBasePriceD18(Vault vault, IFeeManager feeManager, address baseAssetToUse, uint256 totalAssets)
internal
view
returns (uint256 basePriceD18)
{
uint256 totalShares = vault.shareManager().totalShares();
uint256 recipientShares = vault.shareManager().activeSharesOf(feeManager.feeRecipient());
uint256 minPriceD18 = feeManager.minPriceD18(address(vault));
address baseAsset = feeManager.baseAsset(address(vault));
basePriceD18 = Math.mulDiv(
totalShares + feeManager.calculateFee(address(vault), baseAsset, minPriceD18, totalShares - recipientShares),
1 ether,
totalAssets
);
if (baseAsset != address(0)) {
if (baseAssetToUse != baseAsset) {
revert("OracleHelper: invalid base asset");
}
if (0 < minPriceD18 && basePriceD18 < minPriceD18) {
basePriceD18 = _find(
feeManager, vault, basePriceD18, minPriceD18, baseAsset, totalShares, recipientShares, totalAssets
);
}
}
}
function _find(
IFeeManager feeManager,
Vault vault,
uint256 left,
uint256 right,
address baseAsset,
uint256 totalShares,
uint256 recipientShares,
uint256 assets
) internal view returns (uint256 basePriceD18) {
uint256 mid;
basePriceD18 = right;
while (left <= right) {
mid = (left + right) >> 1;
uint256 fee = feeManager.calculateFee(address(vault), baseAsset, mid, totalShares - recipientShares);
if (Math.mulDiv(totalShares + fee, 1 ether, assets) <= mid) {
basePriceD18 = mid;
if (mid == 0) {
break;
}
right = mid - 1;
} else {
left = mid + 1;
}
}
}
}
"
},
"src/vaults/Vault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/factories/IFactoryEntity.sol";
import "../modules/ACLModule.sol";
import "../modules/ShareModule.sol";
import "../modules/VaultModule.sol";
contract Vault is IFactoryEntity, VaultModule, ShareModule {
struct RoleHolder {
bytes32 role;
address holder;
}
constructor(
string memory name_,
uint256 version_,
address depositQueueFactory_,
address redeemQueueFactory_,
address subvaultFactory_,
address verifierFactory_
)
ACLModule(name_, version_)
ShareModule(name_, version_, depositQueueFactory_, redeemQueueFactory_)
VaultModule(name_, version_, subvaultFactory_, verifierFactory_)
{}
function initialize(bytes calldata initParams) external initializer {
{
(
address admin_,
address shareManager_,
address feeManager_,
address riskManager_,
address oracle_,
address defaultDepositHook_,
address defaultRedeemHook_,
uint256 queueLimit_,
RoleHolder[] memory roleHolders
) = abi.decode(
initParams, (address, address, address, address, address, address, address, uint256, RoleHolder[])
);
__BaseModule_init();
__ACLModule_init(admin_);
__ShareModule_init(
shareManager_, feeManager_, oracle_, defaultDepositHook_, defaultRedeemHook_, queueLimit_
);
__VaultModule_init(riskManager_);
for (uint256 i = 0; i < roleHolders.length; i++) {
_grantRole(roleHolders[i].role, roleHolders[i].holder);
}
}
emit Initialized(initParams);
}
}
"
},
"src/ethereum/Constants.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../common/Permissions.sol";
import "../common/ProofLibrary.sol";
import "../common/interfaces/ICowswapSettlement.sol";
import {IWETH as WETHInterface} from "../common/interfaces/IWETH.sol";
import {IWSTETH as WSTETHInterface} from "../common/interfaces/IWSTETH.sol";
import "../common/interfaces/Imports.sol";
import "./strETHLibrary.sol";
import "./tqETHLibrary.sol";
library Constants {
string public constant DEPLOYMENT_NAME = "Mellow";
uint256 public constant DEPLOYMENT_VERSION = 1;
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
address public constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address public constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
address public constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F;
address public constant USDU = 0xdde3eC717f220Fc6A29D6a4Be73F91DA5b718e55;
address public constant USDE = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3;
address public constant SUSDE = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497;
address public constant COWSWAP_SETTLEMENT = 0x9008D19f58AAbD9eD0D60971565AA8510560ab41;
address public constant COWSWAP_VAULT_RELAYER = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110;
address public constant AAVE_CORE = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
address public constant AAVE_PRIME = 0x4e033931ad43597d96D6bcc25c280717730B58B1;
address public constant AAVE_V3_ORACLE = 0x54586bE62E3c3580375aE3723C145253060Ca0C2;
address public constant EIGEN_LAYER_DELEGATION_MANAGER = 0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A;
address public constant EIGEN_LAYER_STRATEGY_MANAGER = 0x858646372CC42E1A627fcE94aa7A7033e7CF075A;
address public constant EIGEN_LAYER_REWARDS_COORDINATOR = 0x7750d328b314EfFa365A0402CcfD489B80B0adda;
address public constant SYMBIOTIC_VAULT_FACTORY = 0xAEb6bdd95c502390db8f52c8909F703E9Af6a346;
address public constant SYMBIOTIC_FARM_FACTORY = 0xFEB871581C2ab2e1EEe6f7dDC7e6246cFa087A23;
address public constant FE_ORACLE = 0x5250Ae8A29A19DF1A591cB1295ea9bF2B0232453;
address public constant STRETH_ARBITRUM_SUBVAULT_0 = 0x222fa99C485a088564eb43fAA50Bc10b2497CDB2;
address public constant ARBITRUM_L1_GATEWAY_ROUTER = 0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef;
address public constant ARBITRUM_L1_TOKEN_GATEWAY_WSTETH = 0x0F25c1DC2a9922304f2eac71DCa9B07E310e8E5a;
address public constant STRETH_PLASMA_SUBVAULT_0 = 0xbbF9400C09B0F649F3156989F1CCb9c016f943bb;
address public constant CCIP_PLASMA_ROUTER = 0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3;
uint64 public constant CCIP_PLASMA_CHAIN_SELECTOR = 9335212494177455608;
address public constant CCIP_ETHEREUM_ROUTER = 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D;
uint64 public constant CCIP_ETHEREUM_CHAIN_SELECTOR = 5009297550715157269;
address public constant STRETH = 0x277C6A642564A91ff78b008022D65683cEE5CCC5;
address public constant STRETH_DEPOSIT_QUEUE_ETH = 0xE707321B887b9da133AC5fCc5eDB78Ab177a152D;
address public constant STRETH_DEPOSIT_QUEUE_WETH = 0x2eA268f1018a4767bF5da42D531Ea9e943942A36;
address public constant STRETH_DEPOSIT_QUEUE_WSTETH = 0x614cb9E9D13712781DfD15aDC9F3DAde60E4eFAb;
address public constant STRETH_REDEEM_QUEUE_WSTETH = 0x1ae8C006b5C97707aa074AaeD42BecAD2CF80Da2;
address public constant CURVE_USDC_USDU_POOL = 0x771c91e699B4B23420de3F81dE2aA38C4041632b;
address public constant CURVE_USDC_USDU_GAUGE = 0x0E2662672adC42Bb73d39196f9f557C11B4FCcf9;
address public constant MORPHO_USDC_ALPHAPING = 0xb0f05E4De970A1aaf77f8C2F823953a367504BA9;
address public constant MORPHO_WETH_ALPHAPING = 0x47fe8Ab9eE47DD65c24df52324181790b9F47EfC;
address public constant BRACKET_FINANCE_WETH_VAULT = 0x3588e6Cb5DCa99E35bA2E2a5D42cdDb46365e71B;
address public constant BRACKET_FINANCE_USDC_VAULT = 0xb8ca40E2c5d77F0Bc1Aa88B2689dddB279F7a5eb;
address public constant RSTETH = 0x7a4EffD87C2f3C55CA251080b1343b605f327E3a;
address public constant CAP_LENDER = 0x15622c3dbbc5614E6DFa9446603c1779647f01FC;
function protocolDeployment() internal pure returns (ProtocolDeployment memory) {
return ProtocolDeployment({
deploymentName: DEPLOYMENT_NAME,
deploymentVersion: DEPLOYMENT_VERSION,
eigenLayerDelegationManager: EIGEN_LAYER_DELEGATION_MANAGER,
eigenLayerStrategyManager: EIGEN_LAYER_STRATEGY_MANAGER,
eigenLayerRewardsCoordinator: EIGEN_LAYER_REWARDS_COORDINATOR,
symbioticVaultFactory: SYMBIOTIC_VAULT_FACTORY,
symbioticFarmFactory: SYMBIOTIC_FARM_FACTORY,
wsteth: WSTETH,
weth: WETH,
proxyAdmin: 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0,
deployer: 0xE98Be1E5538FCbD716C506052eB1Fd5d6fC495A3,
factoryImplementation: Factory(0x0000000397b71C8f3182Fd40D247330D218fdC72),
factory: Factory(0x0000000f9686896836C39cf721141922Ce42639f),
consensusFactory: Factory(0xaEEB06CBd91A18b51a2D30b61477eAeE3a9633C3),
depositQueueFactory: Factory(0xBB92A7B9695750e1234BaB18F83b73686dd09854),
redeemQueueFactory: Factory(0xfe76b5fd238553D65Ce6dd0A572C0fda629F8421),
feeManagerFactory: Factory(0xF7223356819Ea48f25880b6c2ab3e907CC336D45),
oracleFactory: Factory(0x0CdFf250C7a071fdc72340D820C5C8e29507Aaad),
riskManagerFactory: Factory(0xa51E4FA916b939Fa451520D2B7600c740d86E5A0),
shareManagerFactory: Factory(0x952f39AA62E94db3Ad0d1C7D1E43C1a8519E45D8),
subvaultFactory: Factory(0x75FE0d73d3C64cdC1C6449D9F977Be6857c4d011),
vaultFactory: Factory(0x4E38F679e46B3216f0bd4B314E9C429AFfB1dEE3),
verifierFactory: Factory(0x04B30b1e98950e6A13550d84e991bE0d734C2c61),
erc20VerifierFactory: Factory(0x2e234F4E1b7934d5F4bEAE3fF2FDC109f5C42F1d),
symbioticVerifierFactory: Factory(0x41C443F10a92D597e6c9E271140BC94c10f5159F),
eigenLayerVerifierFactory: Factory(0x77A83AcBf7A6df20f1D681b4810437d74AE790F8),
consensusImplementation: Consensus(0x0000000167598d2C78E2313fD5328E16bD9A0b13),
depositQueueImplementation: DepositQueue(payable(0x00000006dA9f179BFE250Dd1c51cD2d3581930c8)),
signatureDepositQueueImplementation: SignatureDepositQueue(payable(0x00000003887dfBCEbD1e4097Ad89B690de7eFbf9)),
redeemQueueImplementation: RedeemQueue(payable(0x0000000285805eac535DADdb9648F1E10DfdC411)),
signatureRedeemQueueImplementation: SignatureRedeemQueue(payable(0x0000000b2082667589A16c4cF18e9f923781c471)),
feeManagerImplementation: FeeManager(0x0000000dE74e5D51651326E0A3e1ACA94bEAF6E1),
oracleImplementation: Oracle(0x0000000F0d3D1c31b72368366A4049C05E291D58),
riskManagerImplementation: RiskManager(0x0000000714cf2851baC1AE2f41871862e9D216fD),
tokenizedShareManagerImplementation: TokenizedShareManager(0x0000000E8eb7173fA1a3ba60eCA325bcB6aaf378),
basicShareManagerImplementation: BasicShareManager(0x00000005564AAE40D88e2F08dA71CBe156767977),
subvaultImplementation: Subvault(payable(0x0000000E535B4E063f8372933A55470e67910a66)),
verifierImplementation: Verifier(0x000000047Fc878662006E78D5174FB4285637966),
vaultImplementation: Vault(payable(0x0000000615B2771511dAa693aC07BE5622869E01)),
bitmaskVerifier: BitmaskVerifier(0x0000000263Fb29C3D6B0C5837883519eF05ea20A),
eigenLayerVerifierImplementation: EigenLayerVerifier(0x00000003F82051A8B2F020B79e94C3DC94E89B81),
erc20VerifierImplementation: ERC20Verifier(0x00000009207D366cBB8549837F8Ae4bf800Af2D6),
symbioticVerifierImplementation: SymbioticVerifier(0x00000000cBC6f5d4348496FfA22Cf014b9DA394B),
vaultConfigurator: VaultConfigurator(0x000000028be48f9E62E13403480B60C4822C5aa5),
basicRedeemHook: BasicRedeemHook(0x0000000637f1b1ccDA4Af2dB6CDDf5e5Ec45fd93),
redirectingDepositHook: RedirectingDepositHook(0x00000004d3B17e5391eb571dDb8fDF95646ca827),
lidoDepositHook: LidoDepositHook(0x000000065d1A7bD71f52886910aaBE6555b7317c),
oracleHelper: OracleHelper(0x000000005F543c38d5ea6D0bF10A50974Eb55E35)
});
}
function getTqETHPreProdDeployment() internal pure returns (VaultDeployment memory $) {
address proxyAdmin = 0xC1211878475Cd017fecb922Ae63cc3815FA45652;
address lazyVaultAdmin = 0xE8bEc6Fb52f01e487415D3Ed3797ab92cBfdF498;
address activeVaultAdmin = 0x7885B30F0DC0d8e1aAf0Ed6580caC22d5D09ff4f;
address oracleUpdater = 0x3F1C3Eb0bC499c1A091B635dEE73fF55E19cdCE9;
address curator = 0x55666095cD083a92E368c0CBAA18d8a10D3b65Ec;
address pauser1 = 0xFeCeb0255a4B7Cd05995A7d617c0D52c994099CF;
address pauser2 = 0x8b7C1b52e2d606a526abD73f326c943c75e45Bd3;
address timelockController = 0xFA4B93A6A482dE973cAcFd89e8CB7a425016Fb89;
ProtocolDeployment memory pd = protocolDeployment();
address deployer = pd.deployer;
address[] memory assets_ = new address[](3);
assets_[0] = Constants.ETH;
assets_[1] = Constants.WETH;
assets_[2] = Constants.WSTETH;
{
Vault.RoleHolder[] memory holders = new Vault.RoleHolder[](42);
uint256 i = 0;
// activeVaultAdmin roles:
holders[i++] = Vault.RoleHolder(Permissions.ACCEPT_REPORT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_MERKLE_ROOT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_CALL_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.DISALLOW_CALL_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_VAULT_LIMIT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_SUBVAULT_LIMIT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_SUBVAULT_ASSETS_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.MODIFY_VAULT_BALANCE_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.MODIFY_SUBVAULT_BALANCE_ROLE, activeVaultAdmin);
// emergeny pauser roles:
holders[i++] = Vault.RoleHolder(Permissions.SET_FLAGS_ROLE, address(timelockController));
holders[i++] = Vault.RoleHolder(Permissions.SET_MERKLE_ROOT_ROLE, address(timelockController));
holders[i++] = Vault.RoleHolder(Permissions.SET_QUEUE_STATUS_ROLE, address(timelockController));
// oracle updater roles:
holders[i++] = Vault.RoleHolder(Permissions.SUBMIT_REPORTS_ROLE, oracleUpdater);
// curator roles:
holders[i++] = Vault.RoleHolder(Permissions.CALLER_ROLE, curator);
holders[i++] = Vault.RoleHolder(Permissions.PULL_LIQUIDITY_ROLE, curator);
holders[i++] = Vault.RoleHolder(Permissions.PUSH_LIQUIDITY_ROLE, curator);
// deployer roles:
holders[i++] = Vault.RoleHolder(Permissions.CREATE_QUEUE_ROLE, deployer);
holders[i++] = Vault.RoleHolder(Permissions.CREATE_SUBVAULT_ROLE, deployer);
holders[i++] = Vault.RoleHolder(Permissions.SET_VAULT_LIMIT_ROLE, deployer);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_SUBVAULT_ASSETS_ROLE, deployer);
holders[i++] = Vault.RoleHolder(Permissions.SET_SUBVAULT_LIMIT_ROLE, deployer);
holders[i++] = Vault.RoleHolder(Permissions.SUBMIT_REPORTS_ROLE, deployer);
holders[i++] = Vault.RoleHolder(Permissions.ACCEPT_REPORT_ROLE, deployer);
assembly {
mstore(holders, i)
}
$.initParams = VaultConfigurator.InitParams({
version: 0,
proxyAdmin: proxyAdmin,
vaultAdmin: lazyVaultAdmin,
shareManagerVersion: 0,
shareManagerParams: abi.encode(bytes32(0), "Theoriq AlphaVault ETH", "tqETH"),
feeManagerVersion: 0,
feeManagerParams: abi.encode(deployer, lazyVaultAdmin, uint24(0), uint24(0), uint24(1e5), uint24(1e4)),
riskManagerVersion: 0,
riskManagerParams: abi.encode(type(int256).max),
oracleVersion: 0,
oracleParams: abi.encode(
IOracle.SecurityParams({
maxAbsoluteDeviation: 0.005 ether,
suspiciousAbsoluteDeviation: 0.001 ether,
maxRelativeDeviationD18: 0.005 ether,
suspiciousRelativeDeviationD18: 0.001 ether,
timeout: 1 hours,
depositInterval: 1 hours,
redeemInterval: 2 days
}),
assets_
),
defaultDepositHook: address(pd.redirectingDepositHook),
defaultRedeemHook: address(pd.basicRedeemHook),
queueLimit: 6,
roleHolders: holders
});
}
$.vault = Vault(payable(0xf328463fb20d9265C612155F4d023f8cD79916C7));
{
Vault.RoleHolder[] memory holders = new Vault.RoleHolder[](50);
uint256 i = 0;
// lazyVaultAdmin roles:
holders[i++] = Vault.RoleHolder(Permissions.DEFAULT_ADMIN_ROLE, lazyVaultAdmin);
// activeVaultAdmin roles:
holders[i++] = Vault.RoleHolder(Permissions.ACCEPT_REPORT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_MERKLE_ROOT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_CALL_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.DISALLOW_CALL_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_VAULT_LIMIT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_SUBVAULT_LIMIT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_SUBVAULT_ASSETS_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.MODIFY_VAULT_BALANCE_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.MODIFY_SUBVAULT_BALANCE_ROLE, activeVaultAdmin);
// emergeny pauser roles:
holders[i++] = Vault.RoleHolder(Permissions.SET_FLAGS_ROLE, address(timelockController));
holders[i++] = Vault.RoleHolder(Permissions.SET_MERKLE_ROOT_ROLE, address(timelockController));
holders[i++] = Vault.RoleHolder(Permissions.SET_QUEUE_STATUS_ROLE, address(timelockController));
// oracle updater roles:
holders[i++] = Vault.RoleHolder(Permissions.SUBMIT_REPORTS_ROLE, oracleUpdater);
// curator roles:
holders[i++] = Vault.RoleHolder(Permissions.CALLER_ROLE, curator);
holders[i++] = Vault.RoleHolder(Permissions.PULL_LIQUIDITY_ROLE, curator);
holders[i++] = Vault.RoleHolder(Permissions.PUSH_LIQUIDITY_ROLE, curator);
assembly {
mstore(holders, i)
}
$.holders = holders;
}
$.depositHook = address(pd.redirectingDepositHook);
$.redeemHook = address(pd.basicRedeemHook);
$.assets = assets_;
$.depositQueueAssets = assets_;
$.redeemQueueAssets = assets_;
$.subvaultVerifiers = new address[](1);
$.subvaultVerifiers[0] = 0x972C2c6b0f11dC748635b00dAD36Bf0BdE08Aa82;
$.timelockControllers = new address[](1);
$.timelockControllers[0] = timelockController;
$.timelockProposers = new address[](2);
$.timelockProposers[0] = lazyVaultAdmin;
$.timelockProposers[1] = deployer;
$.timelockExecutors = new address[](2);
$.timelockExecutors[0] = pauser1;
$.timelockExecutors[1] = pauser2;
$.calls = new SubvaultCalls[](1);
(, IVerifier.VerificationPayload[] memory leaves) = tqETHLibrary.getSubvault0Proofs(curator);
$.calls[0] = tqETHLibrary.getSubvault0SubvaultCalls(curator, leaves);
}
function getStrETHDeployment() internal pure returns (VaultDeployment memory $) {
address proxyAdmin = 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0;
address lazyVaultAdmin = 0xAbE20D266Ae54b9Ae30492dEa6B6407bF18fEeb5;
address activeVaultAdmin = 0xeb1CaFBcC8923eCbc243ff251C385C201A6c734a;
address oracleUpdater = 0xd27fFB15Dd00D5E52aC2BFE6d5AFD36caE850081;
address curator = 0x5Dbf9287787A5825beCb0321A276C9c92d570a75;
address lidoPauser = 0xA916fD5252160A7E56A6405741De76dc0Da5A0Cd;
address mellowPauser = 0xa6278B726d4AA09D14f9E820D7785FAd82E7196F;
address treasury = 0xb1E5a8F26C43d019f2883378548a350ecdD1423B;
address timelockController = 0x8D8b65727729Fb484CB6dc1452D61608a5758596;
ProtocolDeployment memory pd = protocolDeployment();
address deployer = pd.deployer;
address[] memory assets_ = new address[](6);
assets_[0] = Constants.ETH;
assets_[1] = Constants.WETH;
assets_[2] = Constants.WSTETH;
assets_[3] = Constants.USDC;
assets_[4] = Constants.USDT;
assets_[5] = Constants.USDS;
{
Vault.RoleHolder[] memory holders = new Vault.RoleHolder[](42);
uint256 i = 0;
// lazyAdmin
holders[i++] = Vault.RoleHolder(Permissions.DEFAULT_ADMIN_ROLE, lazyVaultAdmin);
// activeVaultAdmin roles:
holders[i++] = Vault.RoleHolder(Permissions.ACCEPT_REPORT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_MERKLE_ROOT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_CALL_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.DISALLOW_CALL_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_VAULT_LIMIT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.SET_SUBVAULT_LIMIT_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.ALLOW_SUBVAULT_ASSETS_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.MODIFY_VAULT_BALANCE_ROLE, activeVaultAdmin);
holders[i++] = Vault.RoleHolder(Permissions.MODIFY_SUBVAULT_BALANCE_ROLE, activeVaultAdmin);
// emergeny pauser roles:
holders[i++] = Vault.RoleHolder(Permissions.SET_FLAGS_ROLE, address(timelockController));
holders[i++] = Vault.RoleHolder(Permissions.SET_MERKLE_ROOT_ROLE, address(timelockController));
holders[i++] = Vault.RoleHolder(Permissions.SET_QUEUE_STATUS_ROLE, address(timelockController));
// oracle updater roles:
holders[i++] = Vault.RoleHolder(Permissions.SUBMIT_REPORTS_ROLE, oracleUpdater);
// curator roles:
holders[i++] = Vault.RoleHolder(Permissions.CALLER_ROLE, curator);
holders[i++] = Vault.RoleHolder(Permissions.PULL_LIQUIDITY_ROLE, curator);
holders[i++] = Vault.RoleHolder(Permissions.PUSH_LIQUIDITY_ROLE, curator);
// deployer roles:
assembly {
mstore(holders, i)
}
$.initParams = VaultConfigurator.InitParams({
version: 0,
proxyAdmin: proxyAdmin,
vaultAdmin: lazyVaultAdmin,
shareManagerVersion: 0,
shareManagerParams: abi.encode(bytes32(0), "Mellow stRATEGY", "strETH"),
feeManagerVersion: 0,
feeManagerParams: abi.encode(deployer, treasury, uint24(0), uint24(0), uint24(1e5), uint24(1e4)),
riskManagerVersion: 0,
riskManagerParams: abi.encode(type(int256).max / 2),
oracleVersion: 0,
oracleParams: abi.encode(
IOracle.SecurityParams({
maxAbsoluteDeviation: 0.005 ether,
suspiciousAbsoluteDeviation: 0.001 ether,
maxRelativeDeviationD18: 0.005 ether,
suspiciousRelativeDeviationD18: 0.001 ether,
timeout: 20 hours,
depositInterval: 1 hours,
redeemInterval: 2 days
}),
assets_
),
defaultDepositHook: address(pd.redirectingDepositHook),
defaultRedeemHook: address(pd.basicRedeemHook),
queueLimit: 4,
roleHolders: holders
});
$.holders = holders;
}
$.vault = Vault(payable(0x277C6A642564A91ff78b008022D65683cEE5CCC5));
$.depositHook = address(pd.redirectingDepositHook);
$.redeemHook = address(pd.basicRedeemHook);
$.assets = assets_;
$.depositQueueAssets =
ArraysLibrary.makeAddressArray(abi.encode(Constants.ETH, Constants.WETH, Constants.WSTETH));
$.redeemQueueAssets = ArraysLibrary.makeAddressArray(abi.encode(Constants.WSTETH));
$.subvaultVerifiers = ArraysLibrary.makeAddressArray(
abi.encode(
0xF4eA276361348b301Ba2296dB909a7c973A15451,
0x02e1C91C4D82af454D892FBE2c5De2c4504b2675,
0x1616d39a201D246cbD1B3B145234638f7719b53A,
0xd662dF7C0FAF0Fe6446638651b05C287806AD1AE
)
);
$.timelockControllers = ArraysLibrary.makeAddressArray(abi.encode(address(timelockController)));
$.timelockProposers = ArraysLibrary.makeAddressArray(abi.encode(lazyVaultAdmin, deployer));
$.timelockExecutors = ArraysLibrary.makeAddressArray(abi.encode(lidoPauser, mellowPauser));
address[] memory subvaults = ArraysLibrary.makeAddressArray(
abi.encode(
0x90c983DC732e65DB6177638f0125914787b8Cb78,
0x893aa69FBAA1ee81B536f0FbE3A3453e86290080,
0x181cB55f872450D16aE858D532B4e35e50eaA76D,
0x9938A09FeA37bA681A1Bd53D33ddDE2dEBEc1dA0
)
);
$.calls = new SubvaultCalls[](4);
{
(, IVerifier.VerificationPayload[] memory leaves) = strETHLibrary.getSubvault0Proofs(curator);
$.calls[0] = strETHLibrary.getSubvault0SubvaultCalls(curator, leaves);
}
{
(, IVerifier.VerificationPayload[] memory leaves) = strETHLibrary.getSubvault1Proofs(curator, subvaults[1]);
$.calls[1] = strETHLibrary.getSubvault1SubvaultCalls(curator, subvaults[1], leaves);
}
{
(, IVerifier.VerificationPayload[] memory leaves) = strETHLibrary.getSubvault2Proofs(curator, subvaults[2]);
$.calls[2] = strETHLibrary.getSubvault2SubvaultCalls(curator, subvaults[2], leaves);
}
{
(, IVerifier.VerificationPayload[] memory leaves) = strETHLibrary.getSubvault3Proofs(curator, subvaults[3]);
$.calls[3] = strETHLibrary.getSubvault3SubvaultCalls(curator, subvaults[3], leaves);
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.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 redemption 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);
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/forge-std/src/console2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import {console as console2} from "./console.sol";
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.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 successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else
Submitted on: 2025-11-03 12:42:50
Comments
Log in to comment.
No comments yet.