From c1ff727e08d60e0966a51e9273416eeb176cfc16 Mon Sep 17 00:00:00 2001 From: obchain Date: Tue, 17 Dec 2024 20:45:06 +0530 Subject: [PATCH 1/6] added vault contract --- contracts/vault/USDCVault.sol | 144 ++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 contracts/vault/USDCVault.sol diff --git a/contracts/vault/USDCVault.sol b/contracts/vault/USDCVault.sol new file mode 100644 index 0000000..9d2e471 --- /dev/null +++ b/contracts/vault/USDCVault.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0 +// ███╗ ███╗ █████╗ ██╗ ██╗ █████╗ +// ████╗ ████║██╔══██╗██║ ██║██╔══██╗ +// ██╔████╔██║███████║███████║███████║ +// ██║╚██╔╝██║██╔══██║██╔══██║██╔══██║ +// ██║ ╚═╝ ██║██║ ██║██║ ██║██║ ██║ +// ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ +// Website: https://maha.xyz +// Discord: https://discord.gg/mahadao +// Twitter: https://twitter.com/mahaxyz_ +pragma solidity 0.8.21; + +import {IERC4626} from "../interfaces/vault/IERC4626.sol"; +import {IPegStabilityModule, ZapSafetyPool} from "../periphery/zaps/implementations/ethereum/ZapSafetyPool.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +interface StableSwap { + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; +} + +contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { + using SafeERC20 for IERC20; + + IERC20 public USDC; + IERC20 public USDE; + IERC4626 public SZAI; + IERC4626 public SUSDE; + IPegStabilityModule public PSM; + ZapSafetyPool public zapContract; + StableSwap public stableSwap; + + function initialize( + string memory _name, + string memory _symbol, + address _usdc, + address _usde, + address _sZAI, + address _sUSDe, + address _psm, + address _zapContract, + address _stableSwapPool + ) external initializer { + USDC = IERC20(_usdc); + USDE = IERC20(_usde); + SZAI = IERC4626(_sZAI); + SUSDE = IERC4626(_sUSDe); + PSM = IPegStabilityModule(_psm); + zapContract = ZapSafetyPool(_zapContract); + stableSwap = StableSwap(_stableSwapPool); + __Ownable_init(msg.sender); + __ERC20_init(_name, _symbol); + } + + function deposit(uint256 assets, address receiver) external returns (uint256) { + // 1. Take the USDC from the User as collateral + USDC.safeTransferFrom(msg.sender, address(this), assets); + // 2. Once we get the USDC do the conversion to get USDe + uint256 balanceUSDC = USDC.balanceOf(address(this)); + require(balanceUSDC > 0, "No USDC"); + uint256 balanceUSDe = _swapUSDCtoUSDe(balanceUSDC); + //3. Now use this USDe to deposit into SUSDe Vault + uint256 shares = SUSDE.deposit(balanceUSDe, address(this)); + uint256 SZAIBalanceBefore = IERC20(address(SZAI)).balanceOf(address(this)); // Before Zap + zapContract.zapIntoSafetyPool(PSM, shares); + uint256 SZAIBalanceAfter = IERC20(address(SZAI)).balanceOf(address(this)); // After Zap + require(SZAIBalanceAfter > SZAIBalanceBefore, "Zap Failed"); + shares = SZAIBalanceAfter - SZAIBalanceBefore; + _mint(receiver, shares); + return shares; + } + + function mint(uint256 shares, address receiver) external returns (uint256) { + // 1. Take USDC from the User as collateral + USDC.safeTransferFrom(msg.sender, address(this), shares); + uint256 balanceUSDC = USDC.balanceOf(address(this)); + require(balanceUSDC > 0, "No USDC"); + uint256 balanceUSDe = _swapUSDCtoUSDe(balanceUSDC); + uint256 assets = SUSDE.mint(balanceUSDe, address(this)); + uint256 SZAIBalanceBefore = IERC20(address(SZAI)).balanceOf(address(this)); // Before Zap + zapContract.zapIntoSafetyPool(PSM, assets); + uint256 SZAIBalanceAfter = IERC20(address(SZAI)).balanceOf(address(this)); // After Zap + require(SZAIBalanceAfter > SZAIBalanceBefore, "Zap Failed"); + assets = SZAIBalanceAfter - SZAIBalanceBefore; + _mint(receiver, assets); + return assets; + } + + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {} + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {} + + function asset() external view returns (address) { + return address(USDC); + } + + function totalAssets() external view returns (uint256) { + return USDC.balanceOf(address(this)); + } + // function convertToAssets( + // uint256 shares + // ) external view returns (uint256 assets) {} + // function convertToShares( + // uint256 assets + // ) external view returns (uint256 shares) {} + // function maxMint( + // address receiver + // ) external view returns (uint256 maxShares) {} + // function maxRedeem( + // address owner + // ) external view returns (uint256 maxShares) {} + // function maxWithdraw( + // address owner + // ) external view returns (uint256 maxAssets) {} + // function previewDeposit( + // uint256 assets + // ) external view returns (uint256 shares) {} + // function previewMint( + // uint256 shares + // ) external view returns (uint256 assets) {} + // function previewRedeem( + // uint256 shares + // ) external view returns (uint256 assets) {} + // function previewWithdraw( + // uint256 assets + // ) external view returns (uint256 shares) {} + // function maxDeposit( + // address receiver + // ) external view returns (uint256 maxAssets) {} + + function _swapUSDCtoUSDe( + uint256 _amount + ) internal returns (uint256) { + int128 indexTokenIn = 1; // USDC + int128 indexTokenOut = 0; // USDe + // Approve Curve Pool to use USDC + USDC.approve(address(stableSwap), _amount); + uint256 beforeBalanceUSDE = USDE.balanceOf(address(this)); // Before Balance + StableSwap(stableSwap).exchange(indexTokenIn, indexTokenOut, _amount, 1); + uint256 afterBalanceUSDE = USDE.balanceOf(address(this)); // After Balance + require(afterBalanceUSDE > beforeBalanceUSDE, "Swap Failed"); + return afterBalanceUSDE; + } +} From 301960384bec54f39ce5ec705cafce04b1a0ccb8 Mon Sep 17 00:00:00 2001 From: obchain Date: Tue, 17 Dec 2024 20:46:31 +0530 Subject: [PATCH 2/6] added interface for vault --- contracts/interfaces/vault/IERC4626.sol | 230 ++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 contracts/interfaces/vault/IERC4626.sol diff --git a/contracts/interfaces/vault/IERC4626.sol b/contracts/interfaces/vault/IERC4626.sol new file mode 100644 index 0000000..d3a67f6 --- /dev/null +++ b/contracts/interfaces/vault/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-3.0 +// ███╗ ███╗ █████╗ ██╗ ██╗ █████╗ +// ████╗ ████║██╔══██╗██║ ██║██╔══██╗ +// ██╔████╔██║███████║███████║███████║ +// ██║╚██╔╝██║██╔══██║██╔══██║██╔══██║ +// ██║ ╚═╝ ██║██║ ██║██║ ██║██║ ██║ +// ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ +// Website: https://maha.xyz +// Discord: https://discord.gg/mahadao +// Twitter: https://twitter.com/mahaxyz_ +pragma solidity 0.8.21; +interface IERC4626 { + 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); +} \ No newline at end of file From 61cf870cae5c8414decd20545f9e97cbee658b42 Mon Sep 17 00:00:00 2001 From: obchain Date: Wed, 18 Dec 2024 20:15:59 +0530 Subject: [PATCH 3/6] feat: added implementation for convertToShares and convertToAssets --- contracts/interfaces/vault/IERC4626.sol | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/interfaces/vault/IERC4626.sol b/contracts/interfaces/vault/IERC4626.sol index d3a67f6..6e5137f 100644 --- a/contracts/interfaces/vault/IERC4626.sol +++ b/contracts/interfaces/vault/IERC4626.sol @@ -9,6 +9,7 @@ // Discord: https://discord.gg/mahadao // Twitter: https://twitter.com/mahaxyz_ pragma solidity 0.8.21; + interface IERC4626 { event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw( @@ -20,6 +21,7 @@ interface IERC4626 { * - 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. @@ -42,9 +44,9 @@ interface IERC4626 { // * “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); + 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. @@ -58,9 +60,9 @@ interface IERC4626 { // * “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); + 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. @@ -227,4 +229,4 @@ interface IERC4626 { // * Those methods should be performed separately. // */ function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); -} \ No newline at end of file +} From ef27d05b45373ec19382d2c443a31cc82b725036 Mon Sep 17 00:00:00 2001 From: obchain Date: Wed, 18 Dec 2024 20:16:57 +0530 Subject: [PATCH 4/6] added natspac comments & convertToShares and convertToAssets --- contracts/vault/USDCVault.sol | 208 ++++++++++++++++++++++++---------- 1 file changed, 147 insertions(+), 61 deletions(-) diff --git a/contracts/vault/USDCVault.sol b/contracts/vault/USDCVault.sol index 9d2e471..0b1e035 100644 --- a/contracts/vault/USDCVault.sol +++ b/contracts/vault/USDCVault.sol @@ -8,6 +8,7 @@ // Website: https://maha.xyz // Discord: https://discord.gg/mahadao // Twitter: https://twitter.com/mahaxyz_ + pragma solidity 0.8.21; import {IERC4626} from "../interfaces/vault/IERC4626.sol"; @@ -23,14 +24,27 @@ interface StableSwap { contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { using SafeERC20 for IERC20; - IERC20 public USDC; - IERC20 public USDE; - IERC4626 public SZAI; - IERC4626 public SUSDE; - IPegStabilityModule public PSM; - ZapSafetyPool public zapContract; - StableSwap public stableSwap; + // State variables for different tokens, vaults, and external contracts + IERC20 public USDC; // USDC token + IERC20 public USDE; // USDe token + IERC4626 public SZAI; // SZAI vault interface + IERC4626 public SUSDE; // SUSDe vault interface + IPegStabilityModule public PSM; // Peg Stability Module (for USDC-to-USDe conversion) + ZapSafetyPool public zapContract; // Zap contract for interacting with safety pool + StableSwap public stableSwap; // StableSwap interface for exchanging assets between pools + /** + * @dev Initializes the contract with necessary addresses and configurations. + * @param _name The name of the vault token. + * @param _symbol The symbol of the vault token. + * @param _usdc The address of the USDC token contract. + * @param _usde The address of the USDe token contract. + * @param _sZAI The address of the SZAI vault contract. + * @param _sUSDe The address of the SUSDe vault contract. + * @param _psm The address of the Peg Stability Module (PSM). + * @param _zapContract The address of the ZapSafetyPool contract. + * @param _stableSwapPool The address of the StableSwap contract. + */ function initialize( string memory _name, string memory _symbol, @@ -53,81 +67,152 @@ contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { __ERC20_init(_name, _symbol); } + /** + * @dev Deposits the given amount of USDC into the vault, swaps for USDe, deposits it into the SUSDe vault, + * then zaps into SZAI and mints shares for the receiver. + * @param assets The amount of USDC to deposit. + * @param receiver The address that will receive the minted shares. + * @return shares The amount of shares minted and assigned to the receiver. + */ function deposit(uint256 assets, address receiver) external returns (uint256) { // 1. Take the USDC from the User as collateral USDC.safeTransferFrom(msg.sender, address(this), assets); - // 2. Once we get the USDC do the conversion to get USDe + + // 2. Convert USDC to USDe uint256 balanceUSDC = USDC.balanceOf(address(this)); require(balanceUSDC > 0, "No USDC"); + uint256 balanceUSDe = _swapUSDCtoUSDe(balanceUSDC); - //3. Now use this USDe to deposit into SUSDe Vault - uint256 shares = SUSDE.deposit(balanceUSDe, address(this)); + + // 3. Deposit the USDe into SUSDe Vault + USDE.approve(address(SUSDE), balanceUSDe); + uint256 susdeShares = SUSDE.deposit(balanceUSDe, address(this)); + + // 4. Zap into SZAI from SUSDe shares uint256 SZAIBalanceBefore = IERC20(address(SZAI)).balanceOf(address(this)); // Before Zap - zapContract.zapIntoSafetyPool(PSM, shares); + IERC20(address(SUSDE)).approve(address(zapContract), susdeShares); + zapContract.zapIntoSafetyPool(PSM, susdeShares); uint256 SZAIBalanceAfter = IERC20(address(SZAI)).balanceOf(address(this)); // After Zap + require(SZAIBalanceAfter > SZAIBalanceBefore, "Zap Failed"); - shares = SZAIBalanceAfter - SZAIBalanceBefore; - _mint(receiver, shares); - return shares; + + // 5. Calculate the number of shares to mint for the receiver based on the assets + uint256 sharesToMint = convertToShares(SZAIBalanceAfter - SZAIBalanceBefore); + + // 6. Mint the calculated number of shares to the receiver + _mint(receiver, sharesToMint); + + return sharesToMint; } - function mint(uint256 shares, address receiver) external returns (uint256) { - // 1. Take USDC from the User as collateral - USDC.safeTransferFrom(msg.sender, address(this), shares); - uint256 balanceUSDC = USDC.balanceOf(address(this)); - require(balanceUSDC > 0, "No USDC"); - uint256 balanceUSDe = _swapUSDCtoUSDe(balanceUSDC); - uint256 assets = SUSDE.mint(balanceUSDe, address(this)); - uint256 SZAIBalanceBefore = IERC20(address(SZAI)).balanceOf(address(this)); // Before Zap - zapContract.zapIntoSafetyPool(PSM, assets); - uint256 SZAIBalanceAfter = IERC20(address(SZAI)).balanceOf(address(this)); // After Zap - require(SZAIBalanceAfter > SZAIBalanceBefore, "Zap Failed"); - assets = SZAIBalanceAfter - SZAIBalanceBefore; - _mint(receiver, assets); - return assets; + /** + * @dev Mints new shares for the receiver (not implemented in this contract). + * @param shares The number of shares to mint. + * @param receiver The address that will receive the minted shares. + * @return assets The number of assets corresponding to the minted shares. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets) {} + + /** + * @dev Withdraws assets (SZAI) from the vault by burning the user's shares. + * @param assets The amount of assets (SZAI) to withdraw. + * @param receiver The address to receive the withdrawn assets. + * @param owner The owner of the shares to burn. + * @return shares The amount of shares burned. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) { + // 1. Calculate the number of shares the user needs to burn to withdraw the requested assets (SZAI) + shares = convertToShares(assets); + + // 2. Ensure the owner has enough shares to burn + uint256 ownerShares = balanceOf(owner); + require(ownerShares >= shares, "Insufficient shares to withdraw"); + + // 3. Check if the vault has enough SZAI for the withdrawal + uint256 vaultSZAI = IERC20(address(SZAI)).balanceOf(address(this)); + require(vaultSZAI >= assets, "Insufficient SZAI in vault for withdrawal"); + + // 4. Transfer the SZAI to the receiver + IERC20(address(SZAI)).safeTransfer(receiver, assets); + + // 5. Burn the shares from the owner's balance + _burn(owner, shares); + + return shares; // Return the number of shares burned } - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {} + /** + * @dev Allows for the redemption of shares for assets (not implemented in this contract). + * @param shares The number of shares to redeem. + * @param receiver The address to receive the redeemed assets. + * @param owner The owner of the shares to redeem. + * @return assets The amount of assets redeemed. + */ function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {} + /** + * @dev Converts a given amount of assets into the equivalent number of shares. + * @param assets The amount of assets to convert. + * @return shares The equivalent number of shares. + */ + function convertToShares( + uint256 assets + ) public view returns (uint256 shares) { + uint256 totalAssetsInVault = totalAssets(); // The total assets held by the vault (SZAI) + uint256 totalSupplyVault = totalSupply(); // The total number of shares in circulation + + // Handle the edge case where no shares exist (empty vault) + if (totalAssetsInVault == 0 || totalSupplyVault == 0) { + // If the vault is empty, the first deposit should mint exactly the number of shares as the amount of assets + return assets; + } + + // Convert assets to shares (based on the ratio of totalAssets / totalSupply) + return (assets * totalSupplyVault) / totalAssetsInVault; + } + + /** + * @dev Converts a given number of shares into the equivalent amount of assets. + * @param shares The number of shares to convert. + * @return assets The equivalent amount of assets. + */ + function convertToAssets( + uint256 shares + ) public view returns (uint256 assets) { + uint256 totalAssetsInVault = totalAssets(); // The total assets held by the vault (SZAI) + uint256 totalSupplyVault = totalSupply(); // The total number of shares in circulation + + // Handle the edge case where no shares exist (empty vault) + if (totalAssetsInVault == 0 || totalSupplyVault == 0) { + // If the vault is empty, the first deposit should mint exactly the number of shares as the amount of assets + return shares; + } + + // Convert shares to assets (based on the ratio of totalAssets / totalSupply) + return (shares * totalAssetsInVault) / totalSupplyVault; + } + + /** + * @dev Returns the address of the asset held by the vault (USDC in this case). + * @return The address of the asset. + */ function asset() external view returns (address) { return address(USDC); } - function totalAssets() external view returns (uint256) { - return USDC.balanceOf(address(this)); + /** + * @dev Returns the total assets held by the vault (SZAI). + * @return The total assets in the vault. + */ + function totalAssets() public view returns (uint256) { + return IERC20(address(SZAI)).balanceOf(address(this)); } - // function convertToAssets( - // uint256 shares - // ) external view returns (uint256 assets) {} - // function convertToShares( - // uint256 assets - // ) external view returns (uint256 shares) {} - // function maxMint( - // address receiver - // ) external view returns (uint256 maxShares) {} - // function maxRedeem( - // address owner - // ) external view returns (uint256 maxShares) {} - // function maxWithdraw( - // address owner - // ) external view returns (uint256 maxAssets) {} - // function previewDeposit( - // uint256 assets - // ) external view returns (uint256 shares) {} - // function previewMint( - // uint256 shares - // ) external view returns (uint256 assets) {} - // function previewRedeem( - // uint256 shares - // ) external view returns (uint256 assets) {} - // function previewWithdraw( - // uint256 assets - // ) external view returns (uint256 shares) {} - // function maxDeposit( - // address receiver - // ) external view returns (uint256 maxAssets) {} + /** + * @dev Swaps USDC for USDe using the StableSwap contract. + * @param _amount The amount of USDC to swap. + * @return The amount of USDe obtained from the swap. + */ function _swapUSDCtoUSDe( uint256 _amount ) internal returns (uint256) { @@ -139,6 +224,7 @@ contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { StableSwap(stableSwap).exchange(indexTokenIn, indexTokenOut, _amount, 1); uint256 afterBalanceUSDE = USDE.balanceOf(address(this)); // After Balance require(afterBalanceUSDE > beforeBalanceUSDE, "Swap Failed"); + USDE.approve(address(SUSDE), afterBalanceUSDE); return afterBalanceUSDE; } } From 82c137228f3bc4882a44c20b7136245d6017d9c7 Mon Sep 17 00:00:00 2001 From: obchain Date: Wed, 18 Dec 2024 20:17:33 +0530 Subject: [PATCH 5/6] added fork test for deposit and withdraw --- test/foundry/USDCVaultFork.sol | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 test/foundry/USDCVaultFork.sol diff --git a/test/foundry/USDCVaultFork.sol b/test/foundry/USDCVaultFork.sol new file mode 100644 index 0000000..dd4f56e --- /dev/null +++ b/test/foundry/USDCVaultFork.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: SEE LICENSE IN LICENSE +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; + +import {IPegStabilityModule} from "contracts/interfaces/core/IPegStabilityModule.sol"; +import {IERC4626} from "contracts/interfaces/vault/IERC4626.sol"; + +import {ZapSafetyPool} from "contracts/periphery/zaps/implementations/ethereum/ZapSafetyPool.sol"; +import {USDCVault} from "contracts/vault/USDCVault.sol"; +import {Test, console} from "lib/forge-std/src/Test.sol"; + +interface StableSwap { + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; +} + +contract USDCVaultFork is Test { + USDCVault public vault; + IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + IERC20 public constant USDE = IERC20(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3); + IERC4626 public constant SUSDE = IERC4626(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497); + IERC4626 public constant SZAI = IERC4626(0x69000195D5e3201Cf73C9Ae4a1559244DF38D47C); + IPegStabilityModule public constant PSM = IPegStabilityModule(0x7DCdE153e4cACe9Ca852590d9654c7694388Db54); + ZapSafetyPool public constant ZAP = ZapSafetyPool(0x7e8503b58f7B734431569A0D3c2Db77c1dbae6e8); + StableSwap public constant POOL = StableSwap(0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72); + string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + uint256 public mainnetFork; + + function setUp() external { + // Create Mainnet Fork here + mainnetFork = vm.createFork(MAINNET_RPC_URL); + vm.selectFork(mainnetFork); + vault = new USDCVault(); + vault.initialize( + "USDC-SZAI Vault", + "USDC-SZAI", + address(USDC), + address(USDE), + address(SZAI), + address(SUSDE), + address(PSM), + address(ZAP), + address(POOL) + ); + } + + function testInitValuesVault() external view { + address vaultAddress = vault.asset(); + assertEq(vaultAddress, address(USDC)); + assertEq(vault.totalAssets(), 0); + } + + function testVaultDeposit() external { + // Need the whale USDC address + address USDCWHALE = 0x412Dd3F282b1FA20d3232d86aE060dEC644249f6; + address bob = makeAddr("1"); + vm.startPrank(USDCWHALE); + // Approve Vault + USDC.approve(address(vault), 100_000_000); + // deposit 100 USDC in the vault + vault.deposit(100_000_000, bob); + vm.stopPrank(); + } + + function testVaultWithdraw() external { + address USDCWHALE = 0x412Dd3F282b1FA20d3232d86aE060dEC644249f6; + address bob = makeAddr("1"); + uint256 depositAmount = 100_000_000; + uint256 withdrawAmount = 5_000_000; + vm.startPrank(USDCWHALE); + // Approve Vault + USDC.approve(address(vault), depositAmount); + // deposit 100 USDC in the vault + vault.deposit(depositAmount, bob); + vm.stopPrank(); + + vm.startPrank(bob); + vault.withdraw(withdrawAmount, address(this), bob); + vm.stopPrank(); + } +} From c42e7a02eaa15d6049047ccf58eb716c79ba0acf Mon Sep 17 00:00:00 2001 From: obchain Date: Fri, 20 Dec 2024 18:27:38 +0530 Subject: [PATCH 6/6] fix: refractor code for the vault & added fork tests --- contracts/interfaces/vault/IERC4626.sol | 155 +----------------------- contracts/vault/USDCVault.sol | 134 ++++++++++---------- test/foundry/USDCVaultFork.sol | 31 ++--- 3 files changed, 87 insertions(+), 233 deletions(-) diff --git a/contracts/interfaces/vault/IERC4626.sol b/contracts/interfaces/vault/IERC4626.sol index 6e5137f..aa2af28 100644 --- a/contracts/interfaces/vault/IERC4626.sol +++ b/contracts/interfaces/vault/IERC4626.sol @@ -23,57 +23,6 @@ interface IERC4626 { */ 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. @@ -89,9 +38,9 @@ interface IERC4626 { // * 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); + function previewDeposit( + uint256 assets + ) external view returns (uint256 shares); /** * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. * @@ -105,55 +54,6 @@ interface IERC4626 { */ 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. // * @@ -170,9 +70,9 @@ interface IERC4626 { // * 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); + function previewWithdraw( + uint256 assets + ) external view returns (uint256 shares); // /** // * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. // * @@ -186,47 +86,4 @@ interface IERC4626 { // * 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); } diff --git a/contracts/vault/USDCVault.sol b/contracts/vault/USDCVault.sol index 0b1e035..02fbe2d 100644 --- a/contracts/vault/USDCVault.sol +++ b/contracts/vault/USDCVault.sol @@ -17,8 +17,12 @@ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/acces import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {console} from "lib/forge-std/src/console.sol"; + interface StableSwap { function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; + function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256); + function get_dx(int128 i, int128 j, uint256 dy) external view returns (uint256); } contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { @@ -79,25 +83,28 @@ contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { USDC.safeTransferFrom(msg.sender, address(this), assets); // 2. Convert USDC to USDe - uint256 balanceUSDC = USDC.balanceOf(address(this)); - require(balanceUSDC > 0, "No USDC"); - - uint256 balanceUSDe = _swapUSDCtoUSDe(balanceUSDC); + uint256 balanceUSDe = _swapUSDCtoUSDe(assets); + console.log("USDe balance this contract have after swap", balanceUSDe); // 3. Deposit the USDe into SUSDe Vault USDE.approve(address(SUSDE), balanceUSDe); uint256 susdeShares = SUSDE.deposit(balanceUSDe, address(this)); - + console.log("After depositing in sUSDe vault get shares", susdeShares); // 4. Zap into SZAI from SUSDe shares uint256 SZAIBalanceBefore = IERC20(address(SZAI)).balanceOf(address(this)); // Before Zap IERC20(address(SUSDE)).approve(address(zapContract), susdeShares); zapContract.zapIntoSafetyPool(PSM, susdeShares); uint256 SZAIBalanceAfter = IERC20(address(SZAI)).balanceOf(address(this)); // After Zap - + console.log("After Zap we get SZAI", SZAIBalanceAfter); require(SZAIBalanceAfter > SZAIBalanceBefore, "Zap Failed"); + uint256 sZAIMinted = SZAIBalanceAfter - SZAIBalanceBefore; + + console.log("SZAI Minted : ", sZAIMinted); // 5. Calculate the number of shares to mint for the receiver based on the assets - uint256 sharesToMint = convertToShares(SZAIBalanceAfter - SZAIBalanceBefore); + uint256 sharesToMint = previewDeposit(sZAIMinted); + + console.log("Shares to Mint", sharesToMint); // 6. Mint the calculated number of shares to the receiver _mint(receiver, sharesToMint); @@ -105,14 +112,6 @@ contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { return sharesToMint; } - /** - * @dev Mints new shares for the receiver (not implemented in this contract). - * @param shares The number of shares to mint. - * @param receiver The address that will receive the minted shares. - * @return assets The number of assets corresponding to the minted shares. - */ - function mint(uint256 shares, address receiver) external returns (uint256 assets) {} - /** * @dev Withdraws assets (SZAI) from the vault by burning the user's shares. * @param assets The amount of assets (SZAI) to withdraw. @@ -122,74 +121,38 @@ contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { */ function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) { // 1. Calculate the number of shares the user needs to burn to withdraw the requested assets (SZAI) - shares = convertToShares(assets); - - // 2. Ensure the owner has enough shares to burn - uint256 ownerShares = balanceOf(owner); - require(ownerShares >= shares, "Insufficient shares to withdraw"); + shares = previewWithdraw(assets); + console.log("Shares to Burn", shares); // 3. Check if the vault has enough SZAI for the withdrawal uint256 vaultSZAI = IERC20(address(SZAI)).balanceOf(address(this)); + console.log("SZAI Balance in Vault: ", vaultSZAI); require(vaultSZAI >= assets, "Insufficient SZAI in vault for withdrawal"); // 4. Transfer the SZAI to the receiver - IERC20(address(SZAI)).safeTransfer(receiver, assets); + IERC20(address(SZAI)).safeTransfer(receiver, shares); // 5. Burn the shares from the owner's balance - _burn(owner, shares); + _burn(owner, assets); return shares; // Return the number of shares burned } - - /** - * @dev Allows for the redemption of shares for assets (not implemented in this contract). - * @param shares The number of shares to redeem. - * @param receiver The address to receive the redeemed assets. - * @param owner The owner of the shares to redeem. - * @return assets The amount of assets redeemed. - */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {} - /** * @dev Converts a given amount of assets into the equivalent number of shares. * @param assets The amount of assets to convert. * @return shares The equivalent number of shares. */ - function convertToShares( + + function previewDeposit( uint256 assets ) public view returns (uint256 shares) { - uint256 totalAssetsInVault = totalAssets(); // The total assets held by the vault (SZAI) - uint256 totalSupplyVault = totalSupply(); // The total number of shares in circulation - - // Handle the edge case where no shares exist (empty vault) - if (totalAssetsInVault == 0 || totalSupplyVault == 0) { - // If the vault is empty, the first deposit should mint exactly the number of shares as the amount of assets - return assets; - } - - // Convert assets to shares (based on the ratio of totalAssets / totalSupply) - return (assets * totalSupplyVault) / totalAssetsInVault; + return _convertSZAIToUSDC(assets); } - /** - * @dev Converts a given number of shares into the equivalent amount of assets. - * @param shares The number of shares to convert. - * @return assets The equivalent amount of assets. - */ - function convertToAssets( - uint256 shares - ) public view returns (uint256 assets) { - uint256 totalAssetsInVault = totalAssets(); // The total assets held by the vault (SZAI) - uint256 totalSupplyVault = totalSupply(); // The total number of shares in circulation - - // Handle the edge case where no shares exist (empty vault) - if (totalAssetsInVault == 0 || totalSupplyVault == 0) { - // If the vault is empty, the first deposit should mint exactly the number of shares as the amount of assets - return shares; - } - - // Convert shares to assets (based on the ratio of totalAssets / totalSupply) - return (shares * totalAssetsInVault) / totalSupplyVault; + function previewWithdraw( + uint256 assets + ) public view returns (uint256 shares) { + return _convertUSDCToSZAI(assets); } /** @@ -200,12 +163,45 @@ contract USDCVault is ERC20Upgradeable, Ownable2StepUpgradeable, IERC4626 { return address(USDC); } - /** - * @dev Returns the total assets held by the vault (SZAI). - * @return The total assets in the vault. - */ - function totalAssets() public view returns (uint256) { - return IERC20(address(SZAI)).balanceOf(address(this)); + function _convertSZAIToUSDC( + uint256 assets + ) internal view returns (uint256) { + int128 indexTokenIn = 1; // USDC + int128 indexTokenOut = 0; // USDe + uint256 SzaiBalance = IERC20(address(SZAI)).balanceOf(address(this)); + console.log("SZAI Balance in contract", SzaiBalance); + // convert SZAI to ZAI First + uint256 ZaiBalance = SZAI.previewWithdraw(SzaiBalance); + console.log("ZAI Assets", ZaiBalance); + // Convert ZAI to SUSDe + uint256 sUsdeBalance = PSM.toCollateralAmount(ZaiBalance); + console.log("SUSDE Balance", sUsdeBalance); + // Convert SUSDE to USDe + uint256 usdeBalance = SUSDE.previewWithdraw(sUsdeBalance); + console.log("USDE Balance", usdeBalance); + // Convert USDE to USDC + uint256 usdcBalance = stableSwap.get_dx(indexTokenIn, indexTokenOut, usdeBalance); + return usdcBalance; + } + + function _convertUSDCToSZAI( + uint256 shares + ) internal view returns (uint256) { + int128 indexTokenIn = 1; // USDC + int128 indexTokenOut = 0; // USDe + // USDC to USDe + uint256 usdeAfterSwap = stableSwap.get_dy(indexTokenIn, indexTokenOut, shares); + console.log("USDE Swap Balance", usdeAfterSwap); + // USDe to SUSDe + uint256 susdeAfterDeposit = SUSDE.previewDeposit(usdeAfterSwap); + console.log("SUSDE shares after deposit", susdeAfterDeposit); + // Call PSM to get the ZAI Mint Amount + uint256 zaiToMint = PSM.mintAmountIn(susdeAfterDeposit); + console.log("ZAI to mint", zaiToMint); + // Call preview deposit to get SZAI + uint256 sZaiAfterDeposit = SZAI.previewDeposit(zaiToMint); + console.log("SZAI After Deposit", sZaiAfterDeposit); + return sZaiAfterDeposit; } /** diff --git a/test/foundry/USDCVaultFork.sol b/test/foundry/USDCVaultFork.sol index dd4f56e..7348a53 100644 --- a/test/foundry/USDCVaultFork.sol +++ b/test/foundry/USDCVaultFork.sol @@ -47,35 +47,36 @@ contract USDCVaultFork is Test { function testInitValuesVault() external view { address vaultAddress = vault.asset(); assertEq(vaultAddress, address(USDC)); - assertEq(vault.totalAssets(), 0); } function testVaultDeposit() external { - // Need the whale USDC address + // Define the addresses for the whale and the recipient address USDCWHALE = 0x412Dd3F282b1FA20d3232d86aE060dEC644249f6; address bob = makeAddr("1"); + uint256 depositAmount = 100_000_000; // USDC to deposit + // Start the prank for the whale (USDCWHALE) vm.startPrank(USDCWHALE); - // Approve Vault - USDC.approve(address(vault), 100_000_000); - // deposit 100 USDC in the vault - vault.deposit(100_000_000, bob); + // Approve Vault to transfer USDC + USDC.approve(address(vault), depositAmount); + // Perform the deposit of 100 million USDC into the vault for 'bob' + vault.deposit(depositAmount, bob); vm.stopPrank(); } - function testVaultWithdraw() external { + function testWithdrawVault() external { + // Define the addresses for the whale and the recipient address USDCWHALE = 0x412Dd3F282b1FA20d3232d86aE060dEC644249f6; address bob = makeAddr("1"); - uint256 depositAmount = 100_000_000; - uint256 withdrawAmount = 5_000_000; + address alice = makeAddr("2"); + uint256 depositAmount = 100_000_000; // USDC to deposit + uint256 withdrawAmount = 50_000_000; + // Start the prank for the whale (USDCWHALE) vm.startPrank(USDCWHALE); - // Approve Vault + // Approve Vault to transfer USDC USDC.approve(address(vault), depositAmount); - // deposit 100 USDC in the vault + // Perform the deposit of 100 million USDC into the vault for 'bob' vault.deposit(depositAmount, bob); - vm.stopPrank(); - - vm.startPrank(bob); - vault.withdraw(withdrawAmount, address(this), bob); + vault.withdraw(withdrawAmount, alice, bob); vm.stopPrank(); } }