diff --git a/nest/src/AggregateToken.sol b/nest/src/AggregateToken.sol index 74f93e8..5ef364c 100644 --- a/nest/src/AggregateToken.sol +++ b/nest/src/AggregateToken.sol @@ -160,20 +160,24 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { /** * @inheritdoc IERC4626 * @dev 1:1 conversion rate between USDT and base asset + * @dev Always round DOWN for convertToShares for user safety */ function convertToShares( uint256 assets ) public view override(ComponentToken, IComponentToken) returns (uint256 shares) { + // Division rounds down by default return assets * _BASE / _getAggregateTokenStorage().askPrice; } /** * @inheritdoc IERC4626 * @dev 1:1 conversion rate between USDT and base asset + * @dev Always round DOWN for convertToAssets for user safety */ function convertToAssets( uint256 shares ) public view override(ComponentToken, IComponentToken) returns (uint256 assets) { + // Division rounds down by default return shares * _getAggregateTokenStorage().bidPrice / _BASE; } diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index dcdf578..8f681d9 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -419,8 +419,8 @@ abstract contract ComponentToken is $.claimableDepositRequest[controller] = 0; $.sharesDepositRequest[controller] = 0; } else { - SafeERC20.safeTransferFrom(IERC20(asset()), controller, address(this), assets); shares = convertToShares(assets); + SafeERC20.safeTransferFrom(IERC20(asset()), controller, address(this), assets); } _mint(receiver, shares); @@ -558,8 +558,8 @@ abstract contract ComponentToken is $.assetsRedeemRequest[controller] = 0; } else { // For sync redemptions, process normally - _burn(controller, shares); assets = convertToAssets(shares); + _burn(controller, shares); } SafeERC20.safeTransfer(IERC20(asset()), receiver, assets); diff --git a/smart-wallets/src/token/YieldToken.sol b/smart-wallets/src/token/YieldToken.sol index 4654e2f..8bba99e 100644 --- a/smart-wallets/src/token/YieldToken.sol +++ b/smart-wallets/src/token/YieldToken.sol @@ -290,7 +290,10 @@ contract YieldToken is return convertToAssets(balanceOf(owner)); } - /// @inheritdoc IERC4626 + /** + * @inheritdoc IERC4626 + * @dev Always rounds down for user safety when converting assets to shares + */ function convertToShares( uint256 assets ) public view override(ERC4626Upgradeable, IComponentToken) returns (uint256 shares) { @@ -302,7 +305,10 @@ contract YieldToken is return (assets * supply) / totalAssets_; } - /// @inheritdoc IERC4626 + /** + * @inheritdoc IERC4626 + * @dev Always rounds down for user safety when converting shares to assets + */ function convertToAssets( uint256 shares ) public view override(ERC4626Upgradeable, IComponentToken) returns (uint256 assets) { @@ -348,8 +354,8 @@ contract YieldToken is revert InvalidCurrencyToken(currencyToken, _getYieldDistributionTokenStorage().currencyToken); } - $.yieldBuffer += amount; _depositYield(currencyTokenAmount); + $.yieldBuffer += amount; } /// @inheritdoc IComponentToken @@ -451,14 +457,20 @@ contract YieldToken is } YieldTokenStorage storage $ = _getYieldTokenStorage(); - assets = convertToAssets(shares); - if ($.claimableDepositRequest[controller] < assets) { - revert InsufficientRequestBalance(controller, assets, 1); + if ($.sharesDepositRequest[controller] < shares) { + revert InsufficientRequestBalance(controller, shares, 1); } + + // Calculate proportional assets based on requested shares + assets = $.claimableDepositRequest[controller].mulDivDown(shares, $.sharesDepositRequest[controller]); + $.claimableDepositRequest[controller] -= assets; $.sharesDepositRequest[controller] -= shares; + // Track managed assets + $.totalManagedAssets += assets; + _mint(receiver, shares); emit Deposit(controller, receiver, assets, shares); @@ -479,7 +491,6 @@ contract YieldToken is YieldTokenStorage storage $ = _getYieldTokenStorage(); - _burn(msg.sender, shares); $.pendingRedeemRequest[controller] += shares; emit RedeemRequest(controller, owner, REQUEST_ID, owner, shares); @@ -539,6 +550,12 @@ contract YieldToken is // Track managed assets $.totalManagedAssets -= assets; + // Check yield buffer + _beforeWithdraw(assets); + + // Burn the shares, when we actually process the redemption + _burn(controller, shares); + if (!IERC20(asset()).transfer(receiver, assets)) { revert InsufficientBalance(IERC20(asset()), address(this), assets); } @@ -588,6 +605,9 @@ contract YieldToken is _beforeWithdraw(assets); + // Burn the shares when we actually process the withdrawal + _burn(controller, shares); + if (!IERC20(asset()).transfer(receiver, assets)) { revert InsufficientBalance(IERC20(asset()), address(this), assets); }