From 0efb7414d40c8d26692112d3c46b2780c090971c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Mon, 9 Dec 2024 19:30:11 -0500 Subject: [PATCH 01/10] [NES-265] [N5] [Critical] ERC4626 preview function override is incorrect (#114) * use convertToAssets and convertToShares for previewMint and previewWithdraw functions * use convertToAssets and convertToShares functions for previewMint and previewWithdraw --- nest/src/ComponentToken.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index cb89485..46a8bab 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -76,7 +76,7 @@ abstract contract ComponentToken is bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); /// @notice Base that is used to divide all price inputs in order to represent e.g. 1.000001 as 1000001e12 uint256 internal constant _BASE = 1e18; - + // Events /** @@ -527,7 +527,7 @@ abstract contract ComponentToken is if (_getComponentTokenStorage().asyncDeposit) { revert Unimplemented(); } - assets = super.previewDeposit(shares); + assets = convertToAssets(shares); } /** @@ -554,7 +554,7 @@ abstract contract ComponentToken is if (_getComponentTokenStorage().asyncRedeem) { revert Unimplemented(); } - shares = super.previewWithdraw(assets); + shares = convertToShares(assets); } /// @inheritdoc IERC7540 From 0939b9568d7f5a361a73ea9726cd07e9d66ec1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Mon, 9 Dec 2024 19:47:02 -0500 Subject: [PATCH 02/10] [NES-273] [N13] Invalid balance acquisition (#116) * change balanceof and assetsOf functions - WIP * correct assetsOf function --- nest/src/token/BoringVaultAdapter.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nest/src/token/BoringVaultAdapter.sol b/nest/src/token/BoringVaultAdapter.sol index 2407ad4..1bc4629 100644 --- a/nest/src/token/BoringVaultAdapter.sol +++ b/nest/src/token/BoringVaultAdapter.sol @@ -448,8 +448,7 @@ abstract contract BoringVaultAdapter is function balanceOf( address account ) public view override(IERC20, ERC20Upgradeable) returns (uint256) { - BoringVaultAdapterStorage storage $ = _getBoringVaultAdapterStorage(); - return $.boringVault.lens.balanceOf(account, $.boringVault.vault); + return super.balanceOf(account); } /** @@ -460,8 +459,7 @@ abstract contract BoringVaultAdapter is function assetsOf( address account ) public view virtual override(ComponentToken) returns (uint256) { - BoringVaultAdapterStorage storage $ = _getBoringVaultAdapterStorage(); - return $.boringVault.lens.balanceOfInAssets(account, $.boringVault.vault, $.boringVault.accountant); + return super.assetsOf(account); } // ========== METADATA OVERRIDES ========== From a78004de7c4954d2ede4daa5dd5b007795ad00ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Mon, 9 Dec 2024 19:48:50 -0500 Subject: [PATCH 03/10] [NES-268] [N8] [Suggestion] Unable to remove componentToken (#118) * removecomponenttoken and update deployment scripts * formatting * aggregatetoken tests * aggregate token tests * formatting * remove removecomponenttoken and ERC1967Proxy from deployment * removecomponentttoken --- nest/script/DeployNestContracts.s.sol | 10 +- nest/script/UpgradeNestContracts.s.sol | 4 +- nest/script/UpgradepUSD.s.sol | 11 +- nest/src/AggregateToken.sol | 45 +++- nest/src/ComponentToken.sol | 1 - nest/test/AggregateToken.t.sol | 309 +++++++++++++++++++++++++ 6 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 nest/test/AggregateToken.t.sol diff --git a/nest/script/DeployNestContracts.s.sol b/nest/script/DeployNestContracts.s.sol index 4a949eb..64e6a6a 100644 --- a/nest/script/DeployNestContracts.s.sol +++ b/nest/script/DeployNestContracts.s.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Script } from "forge-std/Script.sol"; import { Test } from "forge-std/Test.sol"; @@ -13,6 +12,8 @@ import { NestStaking } from "../src/NestStaking.sol"; import { IComponentToken } from "../src/interfaces/IComponentToken.sol"; import { AggregateTokenProxy } from "../src/proxy/AggregateTokenProxy.sol"; import { NestStakingProxy } from "../src/proxy/NestStakingProxy.sol"; + +import { pUSDProxy } from "../src/proxy/pUSDProxy.sol"; import { pUSD } from "../src/token/pUSD.sol"; // Concrete implementation of ComponentToken @@ -43,7 +44,6 @@ contract DeployNestContracts is Script, Test { function run() external { vm.startBroadcast(NEST_ADMIN_ADDRESS); - ERC1967Proxy pUSDProxy = ERC1967Proxy(payable(PUSD_ADDRESS)); AggregateToken aggregateToken = new AggregateToken(); AggregateTokenProxy aggregateTokenProxy = new AggregateTokenProxy( @@ -52,9 +52,9 @@ contract DeployNestContracts is Script, Test { AggregateToken.initialize, ( NEST_ADMIN_ADDRESS, - "Nest Insto Vault", - "NIV", - IComponentToken(address(pUSDProxy)), + "Nest RWA Vault", + "nRWA", + IComponentToken(PUSD_ADDRESS), 1e17, // ask price 1e17 // bid price ) diff --git a/nest/script/UpgradeNestContracts.s.sol b/nest/script/UpgradeNestContracts.s.sol index 382e451..ca1bb8f 100644 --- a/nest/script/UpgradeNestContracts.s.sol +++ b/nest/script/UpgradeNestContracts.s.sol @@ -14,11 +14,13 @@ import { AggregateTokenProxy } from "../src/proxy/AggregateTokenProxy.sol"; contract UpgradeNestContracts is Script, Test { address private constant NEST_ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff; + address private constant BORING_VAULT_ADDRESS = 0xe644F07B1316f28a7F134998e021eA9f7135F351; + UUPSUpgradeable private constant AGGREGATE_TOKEN_PROXY = UUPSUpgradeable(payable(0x659619AEdf381c3739B0375082C2d61eC1fD8835)); // Add the component token addresses - address private constant ASSET_TOKEN = 0xF66DFD0A9304D3D6ba76Ac578c31C84Dc0bd4A00; + address private constant ASSET_TOKEN = 0x2DEc3B6AdFCCC094C31a2DCc83a43b5042220Ea2; // LiquidContinuousMultiTokenVault - Credbull address private constant COMPONENT_TOKEN = 0x4B1fC984F324D2A0fDD5cD83925124b61175f5C6; diff --git a/nest/script/UpgradepUSD.s.sol b/nest/script/UpgradepUSD.s.sol index ce2a86c..f4979c1 100644 --- a/nest/script/UpgradepUSD.s.sol +++ b/nest/script/UpgradepUSD.s.sol @@ -5,7 +5,6 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import { Script } from "forge-std/Script.sol"; -import { Test } from "forge-std/Test.sol"; import { console2 } from "forge-std/console2.sol"; import { pUSDProxy } from "../src/proxy/pUSDProxy.sol"; @@ -13,7 +12,7 @@ import { pUSD } from "../src/token/pUSD.sol"; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -contract UpgradePUSD is Script, Test { +contract UpgradePUSD is Script { // Constants address private constant ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff; @@ -35,6 +34,9 @@ contract UpgradePUSD is Script, Test { uint256 public currentTotalSupply; bool public isConnected; + // small hack to be excluded from coverage report + function test() public { } + function setUp() public { // Try to read implementation slot from proxy, this only works with RPC try vm.load(PUSD_PROXY, ERC1967Utils.IMPLEMENTATION_SLOT) returns (bytes32 implementation) { @@ -56,12 +58,13 @@ contract UpgradePUSD is Script, Test { console2.log("Vault:", currentVault); console2.log("Total Supply:", currentTotalSupply); } else { - vm.skip(true); + //TODO: Check this again + vm.skip(false); isConnected = false; } } catch { console2.log("No implementation found - skipping"); - vm.skip(true); + vm.skip(false); isConnected = false; } } diff --git a/nest/src/AggregateToken.sol b/nest/src/AggregateToken.sol index 5d57091..d782dc6 100644 --- a/nest/src/AggregateToken.sol +++ b/nest/src/AggregateToken.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.25; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { StorageSlot } from "@openzeppelin/contracts/utils/StorageSlot.sol"; import { ComponentToken } from "./ComponentToken.sol"; import { IAggregateToken } from "./interfaces/IAggregateToken.sol"; @@ -55,6 +57,9 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { /// @notice Emitted when the AggregateToken contract is unpaused for deposits event Unpaused(); + /// @notice Emitted when the asset token is updated + event AssetTokenUpdated(IERC20 indexed oldAsset, IERC20 indexed newAsset); + // Errors /** @@ -63,6 +68,9 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { */ error ComponentTokenAlreadyListed(IComponentToken componentToken); + /// @notice Emitted when a ComponentToken is removed from the component token list + event ComponentTokenRemoved(IComponentToken indexed componentToken); + /** * @notice Indicates a failure because the ComponentToken is not in the component token list * @param componentToken ComponentToken that is not in the component token list @@ -124,7 +132,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { uint256 askPrice, uint256 bidPrice ) public initializer { - super.initialize(owner, name, symbol, IERC20(address(asset_)), false, false); + super.initialize(owner, name, symbol, IERC20(address(asset_)), false, true); AggregateTokenStorage storage $ = _getAggregateTokenStorage(); $.componentTokenList.push(asset_); @@ -223,6 +231,41 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { emit ComponentTokenListed(componentToken); } + /** + * @notice Remove a ComponentToken from the component token list + * @dev Only the owner can call this function. The ComponentToken must have zero balance to be removed. + * @param componentToken ComponentToken to remove + */ + function removeComponentToken( + IComponentToken componentToken + ) external nonReentrant onlyRole(ADMIN_ROLE) { + AggregateTokenStorage storage $ = _getAggregateTokenStorage(); + + // Check if component token exists + if (!$.componentTokenMap[componentToken]) { + revert ComponentTokenNotListed(componentToken); + } + + // Check if it's the current asset + if (address(componentToken) == asset()) { + revert ComponentTokenIsAsset(componentToken); + } + + // Remove from mapping + $.componentTokenMap[componentToken] = false; + + // Remove from array by finding and replacing with last element + for (uint256 i = 0; i < $.componentTokenList.length; i++) { + if ($.componentTokenList[i] == componentToken) { + $.componentTokenList[i] = $.componentTokenList[$.componentTokenList.length - 1]; + $.componentTokenList.pop(); + break; + } + } + + emit ComponentTokenUnlisted(componentToken); + } + /** * @notice Buy ComponentToken using `asset` * @dev Only the owner can call this function, will revert if diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index 46a8bab..e115f2b 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -10,7 +10,6 @@ import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/ import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; diff --git a/nest/test/AggregateToken.t.sol b/nest/test/AggregateToken.t.sol new file mode 100644 index 0000000..8a34ad4 --- /dev/null +++ b/nest/test/AggregateToken.t.sol @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +import { AggregateToken } from "../src/AggregateToken.sol"; +import { IComponentToken } from "../src/interfaces/IComponentToken.sol"; +import { AggregateTokenProxy } from "../src/proxy/AggregateTokenProxy.sol"; +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { MockInvalidToken } from "../src/mocks/MockInvalidToken.sol"; +import { MockUSDC } from "../src/mocks/MockUSDC.sol"; + +contract AggregateTokenTest is Test { + + AggregateToken public token; + MockUSDC public usdc; + MockUSDC public newUsdc; + address public owner; + address public user1; + address public user2; + + // Events + event AssetTokenUpdated(IERC20 indexed oldAsset, IERC20 indexed newAsset); + event ComponentTokenListed(IComponentToken indexed componentToken); + event ComponentTokenUnlisted(IComponentToken indexed componentToken); + event ComponentTokenBought( + address indexed buyer, IComponentToken indexed componentToken, uint256 componentTokenAmount, uint256 assets + ); + event ComponentTokenSold( + address indexed seller, IComponentToken indexed componentToken, uint256 componentTokenAmount, uint256 assets + ); + event Paused(); + event Unpaused(); + event ComponentTokenRemoved(IComponentToken indexed componentToken); + + event ComponentTokenBuyRequested( + address indexed buyer, IComponentToken indexed componentToken, uint256 assets, uint256 requestId + ); + + event ComponentTokenSellRequested( + address indexed seller, IComponentToken indexed componentToken, uint256 componentTokenAmount, uint256 requestId + ); + + function setUp() public { + owner = makeAddr("owner"); + user1 = makeAddr("user1"); + user2 = makeAddr("user2"); + + // Deploy tokens + usdc = new MockUSDC(); + newUsdc = new MockUSDC(); + + // Deploy through proxy + AggregateToken impl = new AggregateToken(); + ERC1967Proxy proxy = new AggregateTokenProxy( + address(impl), + abi.encodeCall( + AggregateToken.initialize, + ( + owner, + "Aggregate Token", + "AGG", + IComponentToken(address(usdc)), + 1e18, // 1:1 askPrice + 1e18 // 1:1 bidPrice + ) + ) + ); + token = AggregateToken(address(proxy)); + + // Setup initial balances and approvals + usdc.mint(user1, 1000e6); + vm.prank(user1); + usdc.approve(address(token), type(uint256).max); + } + + function testAddComponentToken() public { + vm.startPrank(owner); + + // Should succeed first time + token.addComponentToken(IComponentToken(address(newUsdc))); + + // Should fail second time + vm.expectRevert( + abi.encodeWithSelector( + AggregateToken.ComponentTokenAlreadyListed.selector, IComponentToken(address(newUsdc)) + ) + ); + token.addComponentToken(IComponentToken(address(newUsdc))); + + vm.stopPrank(); + } + + function testRemoveComponentToken() public { + vm.startPrank(owner); + + // Add a token first + token.addComponentToken(IComponentToken(address(newUsdc))); + + // Should fail when trying to remove current asset + vm.expectRevert( + abi.encodeWithSelector(AggregateToken.ComponentTokenIsAsset.selector, IComponentToken(address(usdc))) + ); + token.removeComponentToken(IComponentToken(address(usdc))); + + // Should succeed with non-asset token + token.removeComponentToken(IComponentToken(address(newUsdc))); + + // Should fail when trying to remove non-existent token + vm.expectRevert( + abi.encodeWithSelector(AggregateToken.ComponentTokenNotListed.selector, IComponentToken(address(newUsdc))) + ); + token.removeComponentToken(IComponentToken(address(newUsdc))); + + vm.stopPrank(); + } + + function testPauseUnpause() public { + vm.startPrank(owner); + + // Should start unpaused + assertFalse(token.isPaused()); + + // Should pause + vm.expectEmit(address(token)); + emit Paused(); + token.pause(); + assertTrue(token.isPaused()); + + // Should fail when already paused + vm.expectRevert(AggregateToken.AlreadyPaused.selector); + token.pause(); + + // Should unpause + vm.expectEmit(address(token)); + emit Unpaused(); + token.unpause(); + assertFalse(token.isPaused()); + + // Should fail when already unpaused + vm.expectRevert(AggregateToken.NotPaused.selector); + token.unpause(); + + vm.stopPrank(); + } + + function testSetPrices() public { + // Grant price updater role + bytes32 priceUpdaterRole = token.PRICE_UPDATER_ROLE(); + vm.startPrank(owner); + token.grantRole(priceUpdaterRole, owner); + + // Test ask price + token.setAskPrice(2e18); + assertEq(token.getAskPrice(), 2e18); + + // Test bid price + token.setBidPrice(1.5e18); + assertEq(token.getBidPrice(), 1.5e18); + + vm.stopPrank(); + } + + function testConversion() public { + // Test convertToShares + assertEq(token.convertToShares(2e18), 2e18); // With askPrice = 1e18, 2 assets = 2 shares + + // Test convertToAssets + assertEq(token.convertToAssets(2e18), 2e18); // With bidPrice = 1e18, 2 shares = 2 assets + + // Test with different prices + vm.startPrank(owner); + token.grantRole(token.PRICE_UPDATER_ROLE(), owner); + token.setAskPrice(2e18); // 2:1 ratio + token.setBidPrice(0.5e18); // 1:2 ratio + vm.stopPrank(); + + assertEq(token.convertToShares(2e18), 1e18); // 2 assets = 1 share at 2:1 ratio + assertEq(token.convertToAssets(2e18), 1e18); // 2 shares = 1 asset at 1:2 ratio + } + + function testDeposit() public { + // Test deposit when paused + vm.prank(owner); + token.pause(); + + vm.expectRevert(AggregateToken.DepositPaused.selector); + token.deposit(1e18, address(this), address(this)); + + // Test successful deposit + vm.prank(owner); + token.unpause(); + + vm.startPrank(user1); + usdc.approve(address(token), 1e18); + uint256 shares = token.deposit(1e18, user1, user1); + assertEq(shares, 1e18); + assertEq(token.balanceOf(user1), 1e18); + vm.stopPrank(); + } + + function testRedeem() public { + // Setup: First deposit some tokens + vm.startPrank(user1); + usdc.approve(address(token), 1e18); + token.deposit(1e18, user1, user1); + + // Test redeem + uint256 assets = token.redeem(1e18, user1, user1); + assertEq(assets, 1e18); + assertEq(token.balanceOf(user1), 0); + assertEq(usdc.balanceOf(user1), 1000e6); // Back to original balance + vm.stopPrank(); + } + + function testTotalAssets() public { + assertEq(token.totalAssets(), 0); + + // Deposit some assets + vm.startPrank(user1); + usdc.approve(address(token), 1e18); + token.deposit(1e18, user1, user1); + assertEq(token.totalAssets(), 1e18); + vm.stopPrank(); + } + + function testApproveComponentToken() public { + vm.startPrank(owner); + + // Test ComponentTokenNotListed error + vm.expectRevert( + abi.encodeWithSelector(AggregateToken.ComponentTokenNotListed.selector, IComponentToken(address(newUsdc))) + ); + token.approveComponentToken(IComponentToken(address(newUsdc)), 1e6); + + // Test successful approval + token.addComponentToken(IComponentToken(address(newUsdc))); + token.approveComponentToken(IComponentToken(address(newUsdc)), 1e6); + assertEq(usdc.allowance(address(token), address(newUsdc)), 1e6); + + vm.stopPrank(); + } + + function testComponentTokenOperations() public { + vm.startPrank(owner); + + // Test buyComponentToken + token.addComponentToken(IComponentToken(address(newUsdc))); + vm.expectEmit(address(token)); + emit ComponentTokenBought(owner, IComponentToken(address(newUsdc)), 1e18, 1e18); + token.buyComponentToken(IComponentToken(address(newUsdc)), 1e18); + + // Test sellComponentToken + vm.expectEmit(address(token)); + emit ComponentTokenSold(owner, IComponentToken(address(newUsdc)), 1e18, 1e18); + token.sellComponentToken(IComponentToken(address(newUsdc)), 1e18); + + // Test requestBuyComponentToken + vm.expectEmit(address(token)); + emit ComponentTokenBuyRequested(owner, IComponentToken(address(newUsdc)), 1e18, 0); + token.requestBuyComponentToken(IComponentToken(address(newUsdc)), 1e18); + + // Test requestSellComponentToken + vm.expectEmit(address(token)); + emit ComponentTokenSellRequested(owner, IComponentToken(address(newUsdc)), 1e18, 0); + token.requestSellComponentToken(IComponentToken(address(newUsdc)), 1e18); + + vm.stopPrank(); + } + + function testGetters() public { + // Test getComponentTokenList + IComponentToken[] memory list = token.getComponentTokenList(); + assertEq(list.length, 1); + assertEq(address(list[0]), address(usdc)); + + // Test getComponentToken + assertTrue(token.getComponentToken(IComponentToken(address(usdc)))); + assertFalse(token.getComponentToken(IComponentToken(address(newUsdc)))); + } + /* + function testSupportsInterface() public { + // Test standard interfaces + assertTrue(token.supportsInterface(type(IERC20).interfaceId)); + assertTrue(token.supportsInterface(type(IERC4626).interfaceId)); + assertTrue(token.supportsInterface(type(IAccessControl).interfaceId)); + + // Test custom interfaces + assertTrue(token.supportsInterface(type(IComponentToken).interfaceId)); + assertTrue(token.supportsInterface(type(IAggregateToken).interfaceId)); + } + */ + // Helper function for access control error message + + function accessControlErrorMessage(address account, bytes32 role) internal pure returns (bytes memory) { + return abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(account), + " is missing role ", + Strings.toHexString(uint256(role), 32) + ); + } + +} From d6b1e50718c1956388fea2db040f7b330b8a2198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Mon, 9 Dec 2024 21:33:32 -0500 Subject: [PATCH 04/10] [NES-263] [N3] [Medium] Wrong asset check when async minting (#120) --- nest/src/ComponentToken.sol | 49 +++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index e115f2b..66e54d6 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -334,23 +334,22 @@ abstract contract ComponentToken is } ComponentTokenStorage storage $ = _getComponentTokenStorage(); - assets = convertToAssets(shares); - if ($.asyncDeposit) { - if ($.claimableDepositRequest[controller] < assets) { - revert InsufficientRequestBalance(controller, assets, 1); + // Check shares directly instead of converting to assets + if ($.sharesDepositRequest[controller] < shares) { + revert InsufficientRequestBalance(controller, shares, 1); } - $.claimableDepositRequest[controller] -= assets; - $.sharesDepositRequest[controller] -= shares; + // Use the pre-calculated assets amount from when deposit was notified + assets = $.claimableDepositRequest[controller]; + $.claimableDepositRequest[controller] = 0; + $.sharesDepositRequest[controller] = 0; } else { - if (!IERC20(asset()).transferFrom(controller, address(this), assets)) { - revert InsufficientBalance(IERC20(asset()), controller, assets); - } + assets = previewMint(shares); + _deposit(msg.sender, receiver, assets, shares); } - _mint(receiver, shares); - - emit Deposit(controller, receiver, assets, shares); + emit Deposit(msg.sender, receiver, assets, shares); + return assets; } /// @inheritdoc IComponentToken @@ -443,7 +442,7 @@ abstract contract ComponentToken is address receiver, address controller ) public virtual override(ERC4626Upgradeable, IERC7540) nonReentrant returns (uint256 shares) { - if (assets == 0) { + if (shares == 0) { revert ZeroAmount(); } if (msg.sender != controller) { @@ -451,23 +450,21 @@ abstract contract ComponentToken is } ComponentTokenStorage storage $ = _getComponentTokenStorage(); - shares = convertToShares(assets); - if ($.asyncRedeem) { - if ($.claimableRedeemRequest[controller] < shares) { - revert InsufficientRequestBalance(controller, shares, 3); + // Use the pre-calculated assets amount from when redeem was notified + if ($.assetsRedeemRequest[controller] < assets) { + revert InsufficientRequestBalance(controller, assets, 3); } - $.claimableRedeemRequest[controller] -= shares; - $.assetsRedeemRequest[controller] -= assets; + shares = $.claimableRedeemRequest[controller]; + $.claimableRedeemRequest[controller] = 0; + $.assetsRedeemRequest[controller] = 0; } else { - _burn(controller, shares); + shares = previewWithdraw(assets); + _withdraw(msg.sender, receiver, msg.sender, assets, shares); } - - if (!IERC20(asset()).transfer(receiver, assets)) { - revert InsufficientBalance(IERC20(asset()), address(this), assets); - } - - emit Withdraw(controller, receiver, controller, assets, shares); + _burn(msg.sender, shares); + emit Withdraw(msg.sender, receiver, msg.sender, assets, shares); + return shares; } // Getter View Functions From 6e621abcb159adb91635a950293cb11a1a042def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Mon, 9 Dec 2024 21:35:25 -0500 Subject: [PATCH 05/10] [NES-272] [N12] Potential risks of using different participation rates (#115) --- nest/src/token/BoringVaultAdapter.sol | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/nest/src/token/BoringVaultAdapter.sol b/nest/src/token/BoringVaultAdapter.sol index 1bc4629..845a6c1 100644 --- a/nest/src/token/BoringVaultAdapter.sol +++ b/nest/src/token/BoringVaultAdapter.sol @@ -46,6 +46,7 @@ abstract contract BoringVaultAdapter is error InvalidSender(); error InvalidController(); error InvalidVault(); + error InvalidAccountant(); error AssetNotSupported(); error TellerPaused(); @@ -360,12 +361,18 @@ abstract contract BoringVaultAdapter is */ function previewDeposit( uint256 assets - ) public view virtual override(ComponentToken) returns (uint256) { + ) public view override(ComponentToken) returns (uint256 shares) { BoringVaultAdapterStorage storage $ = _getBoringVaultAdapterStorage(); - return $.boringVault.lens.previewDeposit( - IERC20(address($.asset)), assets, $.boringVault.vault, $.boringVault.accountant - ); + try $.boringVault.vault.decimals() returns (uint8 shareDecimals) { + try $.boringVault.accountant.getRateInQuote(ERC20(asset())) returns (uint256 rate) { + shares = assets.mulDivDown(10 ** shareDecimals, rate); + } catch { + revert InvalidAccountant(); // Or could create a new error like `InvalidAccountant` + } + } catch { + revert InvalidVault(); + } } /** From d9a09dd6f4687f9c2885b47b411b5a3b8adbcd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Tue, 10 Dec 2024 01:01:04 -0500 Subject: [PATCH 06/10] [NES-261] [N1] [Suggestion] Risks of Token Compatibility (#121) --- nest/src/ComponentToken.sol | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index 66e54d6..2f3dcf1 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -10,6 +10,8 @@ import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/ import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; @@ -252,9 +254,7 @@ abstract contract ComponentToken is revert Unimplemented(); } - if (!IERC20(asset()).transferFrom(owner, address(this), assets)) { - revert InsufficientBalance(IERC20(asset()), owner, assets); - } + SafeERC20.safeTransferFrom(IERC20(asset()), owner, address(this), assets); $.pendingDepositRequest[controller] += assets; emit DepositRequest(controller, owner, REQUEST_ID, owner, assets); @@ -309,9 +309,7 @@ abstract contract ComponentToken is $.claimableDepositRequest[controller] -= assets; $.sharesDepositRequest[controller] -= shares; } else { - if (!IERC20(asset()).transferFrom(controller, address(this), assets)) { - revert InsufficientBalance(IERC20(asset()), controller, assets); - } + SafeERC20.safeTransferFrom(IERC20(asset()), controller, address(this), assets); shares = convertToShares(assets); } @@ -429,9 +427,7 @@ abstract contract ComponentToken is assets = convertToAssets(shares); } - if (!IERC20(asset()).transfer(receiver, assets)) { - revert InsufficientBalance(IERC20(asset()), address(this), assets); - } + SafeERC20.safeTransfer(IERC20(asset()), receiver, assets); emit Withdraw(controller, receiver, controller, assets, shares); } From d6ffde7242e66d22384e1706f391a0cfd23053bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Tue, 10 Dec 2024 02:46:40 -0500 Subject: [PATCH 07/10] add overrides for deposit related functions (#112) --- nest/src/AggregateToken.sol | 55 ++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/nest/src/AggregateToken.sol b/nest/src/AggregateToken.sol index d782dc6..3317c1c 100644 --- a/nest/src/AggregateToken.sol +++ b/nest/src/AggregateToken.sol @@ -174,13 +174,66 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { uint256 assets, address receiver, address controller - ) public override(ComponentToken, IComponentToken) returns (uint256 shares) { + ) public override(ComponentToken, IComponentToken, ERC4626Upgradeable) returns (uint256 shares) { if (_getAggregateTokenStorage().paused) { revert DepositPaused(); } return super.deposit(assets, receiver, controller); } + /** + * @inheritdoc ERC4626Upgradeable + * @dev Overridden to add pause check before deposit + * @param assets Amount of assets to deposit + * @param receiver Address that will receive the shares + * @return shares Amount of shares minted + */ + function deposit( + uint256 assets, + address receiver + ) public virtual override(ERC4626Upgradeable) returns (uint256 shares) { + if (_getAggregateTokenStorage().paused) { + revert DepositPaused(); + } + return super.deposit(assets, receiver); + } + + /** + * @inheritdoc ComponentToken + * @dev Overridden to add pause check before minting + * @param shares Amount of shares to mint + * @param receiver Address that will receive the shares + * @param controller Address that controls the minting + * @return assets Amount of assets deposited + */ + function mint( + uint256 shares, + address receiver, + address controller + ) public virtual override(ComponentToken) returns (uint256 assets) { + if (_getAggregateTokenStorage().paused) { + revert DepositPaused(); + } + return super.mint(shares, receiver, controller); + } + + /** + * @inheritdoc ERC4626Upgradeable + * @dev Overridden to add pause check before minting + * @param shares Amount of shares to mint + * @param receiver Address that will receive the shares + * @return assets Amount of assets deposited + */ + function mint( + uint256 shares, + address receiver + ) public virtual override(ERC4626Upgradeable) returns (uint256 assets) { + if (_getAggregateTokenStorage().paused) { + revert DepositPaused(); + } + return super.mint(shares, receiver); + } + /// @inheritdoc IComponentToken function redeem( uint256 shares, From 46a3e7456b32107e82a7359cf7cc86dc72eb9e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Tue, 10 Dec 2024 10:09:31 -0500 Subject: [PATCH 08/10] [NES-262] [N2] [Critical] Asynchronous deposit/redemption design flaw (#119) * fix deposit and redeem functions * use calculated amounts between async deposit and redeem * Revert "use calculated amounts between async deposit and redeem" This reverts commit e94addd44749d93f8e2d1654cc16780c67b4e3e2. --- nest/src/ComponentToken.sol | 133 +++++++++++++++++++++--- nest/src/interfaces/IComponentToken.sol | 24 +---- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index 2f3dcf1..8aebd78 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -111,6 +111,46 @@ abstract contract ComponentToken is */ error Unauthorized(address sender, address authorizedUser); + /** + * @notice Indicates a failure because the operation was called in async mode + * @dev This error is thrown when trying to perform a synchronous operation while async mode is enabled + */ + error AsyncOperationsEnabled(); + + /** + * @notice Indicates a failure because the operation was called in sync mode + * @dev This error is thrown when trying to perform an asynchronous operation while async mode is disabled + */ + error AsyncOperationsDisabled(); + + /** + * @notice Indicates a failure because there are no claimable deposits for the controller + * @dev This error is thrown when trying to claim a deposit but either the assets + * or shares amount in the request is zero + */ + error NoClaimableDeposit(); + + /** + * @notice Indicates a failure because there are no claimable redemptions for the controller + * @dev This error is thrown when trying to claim a redemption but either the assets + * or shares amount in the request is zero + */ + error NoClaimableRedeem(); + + /** + * @notice Indicates a failure because the deposit amount doesn't match the claimable amount + * @param provided Amount of assets provided for deposit + * @param required Amount of assets required (claimable amount) + */ + error InvalidDepositAmount(uint256 provided, uint256 required); + + /** + * @notice Indicates a failure because the redeem amount doesn't match the claimable amount + * @param provided Amount of shares provided for redemption + * @param required Amount of shares required (claimable amount) + */ + error InvalidRedeemAmount(uint256 provided, uint256 required); + /** * @notice Indicates a failure because the controller does not have enough requested * @param controller Address of the controller who does not have enough requested @@ -301,21 +341,30 @@ abstract contract ComponentToken is } ComponentTokenStorage storage $ = _getComponentTokenStorage(); + if ($.asyncDeposit) { - if ($.claimableDepositRequest[controller] < assets) { - revert InsufficientRequestBalance(controller, assets, 1); - } + // For async deposits, we must use the full claimable amount + uint256 claimableAssets = $.claimableDepositRequest[controller]; shares = $.sharesDepositRequest[controller]; - $.claimableDepositRequest[controller] -= assets; - $.sharesDepositRequest[controller] -= shares; + + if (claimableAssets == 0 || shares == 0) { + revert NoClaimableDeposit(); + } + if (assets != claimableAssets) { + revert InvalidDepositAmount(assets, claimableAssets); + } + + // Reset state atomically + $.claimableDepositRequest[controller] = 0; + $.sharesDepositRequest[controller] = 0; } else { SafeERC20.safeTransferFrom(IERC20(asset()), controller, address(this), assets); shares = convertToShares(assets); } _mint(receiver, shares); - emit Deposit(controller, receiver, assets, shares); + return shares; } /// @inheritdoc IERC7540 @@ -401,7 +450,14 @@ abstract contract ComponentToken is emit RedeemNotified(controller, assets, shares); } - /// @inheritdoc IERC7540 + /** + * @notice Fulfill a synchronous request to redeem assets by transferring assets to the receiver + * @dev This function can only be called when async redemptions are disabled + * @param shares Amount of shares to redeem + * @param receiver Address to receive the assets + * @param controller Controller of the request + * @return assets Amount of assets sent to the receiver + */ function redeem( uint256 shares, address receiver, @@ -415,14 +471,24 @@ abstract contract ComponentToken is } ComponentTokenStorage storage $ = _getComponentTokenStorage(); + if ($.asyncRedeem) { - if ($.claimableRedeemRequest[controller] < shares) { - revert InsufficientRequestBalance(controller, shares, 3); - } + // For async redemptions, we must use the full claimable amount + uint256 claimableShares = $.claimableRedeemRequest[controller]; assets = $.assetsRedeemRequest[controller]; - $.claimableRedeemRequest[controller] -= shares; - $.assetsRedeemRequest[controller] -= assets; + + if (claimableShares == 0 || assets == 0) { + revert NoClaimableRedeem(); + } + if (shares != claimableShares) { + revert InvalidRedeemAmount(shares, claimableShares); + } + + // Reset state atomically + $.claimableRedeemRequest[controller] = 0; + $.assetsRedeemRequest[controller] = 0; } else { + // For sync redemptions, process normally _burn(controller, shares); assets = convertToAssets(shares); } @@ -430,6 +496,49 @@ abstract contract ComponentToken is SafeERC20.safeTransfer(IERC20(asset()), receiver, assets); emit Withdraw(controller, receiver, controller, assets, shares); + return assets; + } + + /** + * @notice Claim an approved asynchronous redeem request and transfer assets to the receiver + * @dev This function can only be called when async redemptions are enabled + * and will revert if there are no claimable redemptions for the controller. + * All state for the request is atomically reset after a successful claim. + * @param receiver Address to receive the redeemed assets + * @param controller Controller of the redeem request + * @return assets Amount of assets sent to the receiver + * @return shares Amount of shares that were redeemed + */ + function claimRedeem( + address receiver, + address controller + ) public virtual nonReentrant returns (uint256 assets, uint256 shares) { + if (msg.sender != controller) { + revert Unauthorized(msg.sender, controller); + } + + ComponentTokenStorage storage $ = _getComponentTokenStorage(); + if (!$.asyncRedeem) { + revert AsyncOperationsDisabled(); + } + + shares = $.claimableRedeemRequest[controller]; + assets = $.assetsRedeemRequest[controller]; + + if (shares == 0 || assets == 0) { + revert NoClaimableRedeem(); + } + + // Reset state atomically + $.claimableRedeemRequest[controller] = 0; + $.assetsRedeemRequest[controller] = 0; + + if (!IERC20(asset()).transfer(receiver, assets)) { + revert InsufficientBalance(IERC20(asset()), address(this), assets); + } + + emit Withdraw(controller, receiver, controller, assets, shares); + return (assets, shares); } /// @inheritdoc IERC7540 diff --git a/nest/src/interfaces/IComponentToken.sol b/nest/src/interfaces/IComponentToken.sol index 1df421a..0d7e129 100644 --- a/nest/src/interfaces/IComponentToken.sol +++ b/nest/src/interfaces/IComponentToken.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; interface IComponentToken { - // Events + // User Functions /** * @notice Emitted when the owner of some assets submits a request to buy shares @@ -29,28 +29,6 @@ interface IComponentToken { address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 shares ); - /** - * @notice Emitted when a deposit request is complete - * @param sender Controller of the request - * @param owner Source of the assets to deposit - * @param assets Amount of `asset` that has been deposited - * @param shares Amount of shares that has been received in exchange - */ - // event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); - - /** - * @notice Emitted when a redeem request is complete - * @param sender Controller of the request - * @param receiver Address to receive the assets - * @param owner Source of the shares to redeem - * @param assets Amount of `asset` that has been received in exchange - * @param shares Amount of shares that has been redeemed - */ - // event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 - // shares); - - // User Functions - /** * @notice Transfer assets from the owner into the vault and submit a request to buy shares * @param assets Amount of `asset` to deposit From bf6895bfeb31883ca0811b3d894cff914663f76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Tue, 10 Dec 2024 16:45:15 -0500 Subject: [PATCH 09/10] [NES-294] [Fix] Slowmist remediations update (#122) * slowmist review update * fix mint function * double check functions, formatting * early exit --- nest/src/AggregateToken.sol | 2 +- nest/src/ComponentToken.sol | 32 +++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/nest/src/AggregateToken.sol b/nest/src/AggregateToken.sol index 3317c1c..aa84180 100644 --- a/nest/src/AggregateToken.sol +++ b/nest/src/AggregateToken.sol @@ -174,7 +174,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { uint256 assets, address receiver, address controller - ) public override(ComponentToken, IComponentToken, ERC4626Upgradeable) returns (uint256 shares) { + ) public override(ComponentToken, IComponentToken) returns (uint256 shares) { if (_getAggregateTokenStorage().paused) { revert DepositPaused(); } diff --git a/nest/src/ComponentToken.sol b/nest/src/ComponentToken.sol index 8aebd78..55a953d 100644 --- a/nest/src/ComponentToken.sol +++ b/nest/src/ComponentToken.sol @@ -386,13 +386,21 @@ abstract contract ComponentToken is if ($.sharesDepositRequest[controller] < shares) { revert InsufficientRequestBalance(controller, shares, 1); } - // Use the pre-calculated assets amount from when deposit was notified + + // Get the pre-calculated values + uint256 claimableShares = $.sharesDepositRequest[controller]; + + // Verify shares match exactly + if (shares != claimableShares) { + revert InvalidDepositAmount(shares, claimableShares); + } + assets = $.claimableDepositRequest[controller]; $.claimableDepositRequest[controller] = 0; $.sharesDepositRequest[controller] = 0; } else { assets = previewMint(shares); - _deposit(msg.sender, receiver, assets, shares); + SafeERC20.safeTransferFrom(IERC20(asset()), controller, address(this), assets); } _mint(receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); @@ -547,7 +555,7 @@ abstract contract ComponentToken is address receiver, address controller ) public virtual override(ERC4626Upgradeable, IERC7540) nonReentrant returns (uint256 shares) { - if (shares == 0) { + if (assets == 0) { revert ZeroAmount(); } if (msg.sender != controller) { @@ -556,19 +564,29 @@ abstract contract ComponentToken is ComponentTokenStorage storage $ = _getComponentTokenStorage(); if ($.asyncRedeem) { - // Use the pre-calculated assets amount from when redeem was notified if ($.assetsRedeemRequest[controller] < assets) { revert InsufficientRequestBalance(controller, assets, 3); } + // Get the pre-calculated values + uint256 claimableAssets = $.assetsRedeemRequest[controller]; shares = $.claimableRedeemRequest[controller]; + + // Verify assets match exactly + if (assets != claimableAssets) { + revert InvalidRedeemAmount(assets, claimableAssets); + } + + // Reset state atomically $.claimableRedeemRequest[controller] = 0; $.assetsRedeemRequest[controller] = 0; + + // No _burn needed here as shares were already burned in requestRedeem + SafeERC20.safeTransfer(IERC20(asset()), receiver, assets); + emit Withdraw(controller, receiver, controller, assets, shares); } else { shares = previewWithdraw(assets); - _withdraw(msg.sender, receiver, msg.sender, assets, shares); + _withdraw(controller, receiver, controller, assets, shares); } - _burn(msg.sender, shares); - emit Withdraw(msg.sender, receiver, msg.sender, assets, shares); return shares; } From 1b4ab430e1fddf2161fe6423d442e9a166529eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20G=C3=BCneysel?= Date: Tue, 10 Dec 2024 18:12:21 -0500 Subject: [PATCH 10/10] fix overrides, add nonreentrants (#123) --- nest/src/AggregateToken.sol | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nest/src/AggregateToken.sol b/nest/src/AggregateToken.sol index aa84180..0bb4d3a 100644 --- a/nest/src/AggregateToken.sol +++ b/nest/src/AggregateToken.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +import { ERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { StorageSlot } from "@openzeppelin/contracts/utils/StorageSlot.sol"; import { ComponentToken } from "./ComponentToken.sol"; import { IAggregateToken } from "./interfaces/IAggregateToken.sol"; @@ -174,7 +173,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { uint256 assets, address receiver, address controller - ) public override(ComponentToken, IComponentToken) returns (uint256 shares) { + ) public override(ComponentToken, IComponentToken) nonReentrant returns (uint256 shares) { if (_getAggregateTokenStorage().paused) { revert DepositPaused(); } @@ -191,7 +190,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { function deposit( uint256 assets, address receiver - ) public virtual override(ERC4626Upgradeable) returns (uint256 shares) { + ) public override(ERC4626Upgradeable, IERC4626) nonReentrant returns (uint256 shares) { if (_getAggregateTokenStorage().paused) { revert DepositPaused(); } @@ -210,7 +209,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { uint256 shares, address receiver, address controller - ) public virtual override(ComponentToken) returns (uint256 assets) { + ) public override(ComponentToken) nonReentrant returns (uint256 assets) { if (_getAggregateTokenStorage().paused) { revert DepositPaused(); } @@ -227,7 +226,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { function mint( uint256 shares, address receiver - ) public virtual override(ERC4626Upgradeable) returns (uint256 assets) { + ) public override(ERC4626Upgradeable, IERC4626) nonReentrant returns (uint256 assets) { if (_getAggregateTokenStorage().paused) { revert DepositPaused(); } @@ -239,7 +238,7 @@ contract AggregateToken is ComponentToken, IAggregateToken, ERC1155Holder { uint256 shares, address receiver, address controller - ) public override(ComponentToken, IComponentToken) returns (uint256 assets) { + ) public override(ComponentToken, IComponentToken) nonReentrant returns (uint256 assets) { return super.redeem(shares, receiver, controller); }