diff --git a/packages/contracts/package.json b/packages/contracts/package.json index fea1af27..781b7f56 100755 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -10,7 +10,7 @@ "dev": "yarn rm-dbdata && anvil --config-out localhost.json & make deploy-local", "build": "forge build && yarn gen-types", "test": "forge test", - "coverage": "forge coverage --report lcov && genhtml lcov.info -o out/test-reports/coverage --ignore-errors inconsistent", + "coverage": "forge coverage --report lcov && genhtml lcov.info --branch-coverage -o out/test-reports/coverage --ignore-errors inconsistent", "format": "forge fmt && prettier './script/**/*.js' --write", "lint": "forge fmt && eslint --fix --ignore-path .gitignore && yarn solhint './*(test|src)/**/*.sol'", "db-check": "tsc && node ./script/utils/checkDb.js", diff --git a/packages/contracts/test/src/token/ERC1155/MultiTokenVaultTest.t.sol b/packages/contracts/test/src/token/ERC1155/MultiTokenVaultTest.t.sol index 67ee065e..9e4c0099 100644 --- a/packages/contracts/test/src/token/ERC1155/MultiTokenVaultTest.t.sol +++ b/packages/contracts/test/src/token/ERC1155/MultiTokenVaultTest.t.sol @@ -38,7 +38,7 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { _testParams3 = TestParamSet.TestParam({ principal: 700 * _scale, depositPeriod: 30, redeemPeriod: 55 }); } - function test__MultiTokenVaulTest__SimpleDeposit() public { + function test__MultiTokenVaultTest__SimpleDeposit() public { uint256 assetToSharesRatio = 2; IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); @@ -64,7 +64,32 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { testVaultAtOffsets(_alice, vault, _testParams1); } - function test__MultiTokenVaulTest__DepositAndRedeem() public { + function test__MultiTokenVaultTest__RevertWhen_DepositExceedsMax() public { + uint256 assetToSharesRatio = 2; + + MultiTokenVaultDailyPeriods vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + uint256 _maxDeposit = 250 * _scale; + vault.setMaxDeposit(_maxDeposit); + + address vaultAddress = address(vault); + vm.startPrank(_alice); + _asset.approve(vaultAddress, _testParams1.principal); + + // deposit amount > max deposit amount should fail. + vm.expectRevert( + abi.encodeWithSelector( + MultiTokenVault.MultiTokenVault__ExceededMaxDeposit.selector, + _alice, + vault.currentPeriodsElapsed(), + _testParams1.principal, + _maxDeposit + ) + ); + vault.deposit(_testParams1.principal, _alice); + vm.stopPrank(); + } + + function test__MultiTokenVaultTest__DepositAndRedeem() public { uint256 assetToSharesRatio = 3; _transferAndAssert(_asset, _owner, _charlie, 100_000 * _scale); @@ -74,7 +99,7 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { testVaultAtOffsets(_charlie, vault, _testParams1); } - function test__MultiTokenVaulTest__RedeemBeforeDepositPeriodReverts() public { + function test__MultiTokenVaultTest__RedeemBeforeDepositPeriodReverts() public { MultiTokenVault vault = _createMultiTokenVault(_asset, 1, 10); TestParamSet.TestParam memory testParam = @@ -92,7 +117,7 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { vault.redeemForDepositPeriod(1, _alice, _alice, testParam.depositPeriod, testParam.redeemPeriod); } - function test__MultiTokenVaulTest__CurrentBeforeRedeemPeriodReverts() public { + function test__MultiTokenVaultTest__CurrentBeforeRedeemPeriodReverts() public { MultiTokenVault vault = _createMultiTokenVault(_asset, 1, 10); TestParamSet.TestParam memory testParam = @@ -113,7 +138,7 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { vault.redeemForDepositPeriod(1, _alice, _alice, testParam.depositPeriod, testParam.redeemPeriod); } - function test__MultiTokenVaulTest__RedeemOverMaxSharesReverts() public { + function test__MultiTokenVaultTest__RedeemOverMaxSharesReverts() public { MultiTokenVault vault = _createMultiTokenVault(_asset, 1, 10); TestParamSet.TestParam memory testParam = @@ -135,7 +160,7 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { vault.redeemForDepositPeriod(sharesToRedeem, _alice, _alice, testParam.depositPeriod, testParam.redeemPeriod); } - function test__MultiTokenVaulTest__MultipleDepositsAndRedeem() public { + function test__MultiTokenVaultTest__MultipleDepositsAndRedeem() public { uint256 assetToSharesRatio = 4; // setup @@ -233,11 +258,12 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { assertEq(assets, _asset.balanceOf(receiverAccount), "receiver did not receive assets"); } - function test__MultiTokenVaulTest__BatchFunctions() public { + function test__MultiTokenVaultTest__BatchFunctions() public { uint256 assetToSharesRatio = 2; uint256 redeemPeriod = 2001; TestParamSet.TestParam[] memory _batchTestParams = new TestParamSet.TestParam[](3); + TestParamSet.TestParam[] memory _batchTestParamsToRevert = new TestParamSet.TestParam[](2); _batchTestParams[0] = TestParamSet.TestParam({ principal: 1001 * _scale, depositPeriod: 1, redeemPeriod: redeemPeriod }); @@ -246,15 +272,30 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { _batchTestParams[2] = TestParamSet.TestParam({ principal: 3003 * _scale, depositPeriod: 303, redeemPeriod: redeemPeriod }); + _batchTestParamsToRevert[0] = + TestParamSet.TestParam({ principal: 1001 * _scale, depositPeriod: 1, redeemPeriod: redeemPeriod }); + _batchTestParamsToRevert[1] = + TestParamSet.TestParam({ principal: 2002 * _scale, depositPeriod: 202, redeemPeriod: redeemPeriod }); + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); TestParamSet.TestUsers memory testUsers = TestParamSet.toSingletonUsers(_alice); uint256[] memory shares = _testDepositOnly(testUsers, vault, _batchTestParams); uint256[] memory depositPeriods = _batchTestParams.depositPeriods(); + uint256[] memory depositPeriodsToRevert = _batchTestParamsToRevert.depositPeriods(); // ------------------------ batch convert to assets ------------------------ uint256[] memory assets = vault.convertToAssetsForDepositPeriodBatch(shares, depositPeriods, redeemPeriod); + vm.expectRevert( + abi.encodeWithSelector( + MultiTokenVault.MultiTokenVault__InvalidArrayLength.selector, + depositPeriodsToRevert.length, + shares.length + ) + ); + vault.convertToAssetsForDepositPeriodBatch(shares, depositPeriodsToRevert, redeemPeriod); + assertEq(3, assets.length, "assets are wrong length"); assertEq( assets[0], @@ -292,6 +333,172 @@ contract MultiTokenVaultTest is IMultiTokenVaultTestBase { vault.deposit(1e6 - 1, _alice); } + function test__MultiTokenVaultTest__SafeTransferFrom() public { + uint256 assetToSharesRatio = 2; + + TestParamSet.TestParam memory testParams = + TestParamSet.TestParam({ principal: 100 * _scale, depositPeriod: 0, redeemPeriod: 10 }); + + // Step 1: Create and set up the vault + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + // Step 2: Deposit some assets for _alice to receive shares + vm.startPrank(_alice); + _asset.approve(address(vault), testParams.principal); + uint256 shares = vault.deposit(testParams.principal, _alice); + vm.stopPrank(); + + // Verify _alice has received the shares + assertEq( + vault.balanceOf(_alice, testParams.depositPeriod), shares, "_alice should have the shares after deposit" + ); + + // Step 3: Perform the safe transfer from _alice to _bob + vm.startPrank(_alice); + vault.safeTransferFrom(_alice, _bob, testParams.depositPeriod, shares, ""); + vm.stopPrank(); + + // Step 4: Verify the transfer was successful + assertEq(vault.balanceOf(_alice, testParams.depositPeriod), 0, "_alice should have no shares after transfer"); + assertEq(vault.balanceOf(_bob, testParams.depositPeriod), shares, "_bob should have the transferred shares"); + } + + function test__MultiTokenVaultTest__ConvertToSharesForDepositPeriod() public { + // Assuming the asset to shares ratio is set to a fixed value for testing. + uint256 assetToSharesRatio = 2; + uint256 depositPeriod = 1; + + // Step 1: Create and initialize the vault with a dummy asset + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + // Typical Case: Convert a positive asset amount to shares + uint256 assets = 500 * _scale; + uint256 expectedShares = assets / assetToSharesRatio; + uint256 shares = vault.convertToSharesForDepositPeriod(assets, depositPeriod); + assertEq(shares, expectedShares, "Conversion to shares did not match expected value"); + + // Edge Case: Convert zero assets to shares + assets = 0; + expectedShares = 0; + shares = vault.convertToSharesForDepositPeriod(assets, depositPeriod); + assertEq(shares, expectedShares, "Conversion of zero assets to shares failed"); + + // Edge Case: Convert maximum assets to shares + assets = type(uint256).max; + expectedShares = assets / assetToSharesRatio; + shares = vault.convertToSharesForDepositPeriod(assets, depositPeriod); + assertEq(shares, expectedShares, "Conversion of max assets to shares failed"); + } + + function test__MultiTokenVaultTest__ConvertToAssetsForDepositPeriod() public { + uint256 assetToSharesRatio = 2; + + TestParamSet.TestParam memory testParams = + TestParamSet.TestParam({ principal: 500 * _scale, depositPeriod: 0, redeemPeriod: 30 }); + + // Step 1: Create and initialize the vault with a dummy asset + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + // Typical Case: Convert a positive share amount to assets + uint256 shares = testParams.principal / assetToSharesRatio; + uint256 expectedAssets = shares * assetToSharesRatio + _expectedReturns(shares, vault, testParams); + uint256 assets = + vault.convertToAssetsForDepositPeriod(shares, testParams.depositPeriod, testParams.redeemPeriod); + assertEq(assets, expectedAssets, "Conversion to assets did not match expected value"); + + // Edge Case: Convert zero shares to assets + shares = 0; + expectedAssets = 0; + assets = vault.convertToAssetsForDepositPeriod(shares, testParams.depositPeriod, testParams.redeemPeriod); + assertEq(assets, expectedAssets, "Conversion of zero shares to assets failed"); + + // Edge Case: Convert maximum shares to assets + testParams = + TestParamSet.TestParam({ principal: type(uint128).max * _scale, depositPeriod: 0, redeemPeriod: 30 }); + shares = testParams.principal / assetToSharesRatio; + expectedAssets = shares * assetToSharesRatio + _expectedReturns(shares, vault, testParams); + assets = vault.convertToAssetsForDepositPeriod(shares, testParams.depositPeriod, testParams.redeemPeriod); + assertEq(assets, expectedAssets, "Conversion of max shares to assets failed"); + } + + function test__MultiTokenVaultTest__PreviewDeposit() public { + uint256 assetToSharesRatio = 2; + + TestParamSet.TestParam memory testParams = + TestParamSet.TestParam({ principal: 500 * _scale, depositPeriod: 0, redeemPeriod: 30 }); + + uint256 expectedShares = testParams.principal / assetToSharesRatio; + + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + uint256 shares = vault.previewDeposit(testParams.principal); + assertEq(shares, expectedShares, "Preview deposit conversion did not match expected value"); + } + + function test__MultiTokenVaultTest__PreviewRedeemForDepositPeriod() public { + uint256 assetToSharesRatio = 2; + + TestParamSet.TestParam memory testParams = + TestParamSet.TestParam({ principal: 500 * _scale, depositPeriod: 0, redeemPeriod: 30 }); + + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + uint256 shares = testParams.principal / assetToSharesRatio; + uint256 expectedAssets = shares * assetToSharesRatio + _expectedReturns(shares, vault, testParams); + + uint256 assets = vault.previewRedeemForDepositPeriod(shares, testParams.depositPeriod); + assertEq(assets, expectedAssets, "Preview redeem conversion did not match expected value"); + } + + function test__MultiTokenVaultTest__IsApprovedForAll() public { + address operator = makeAddr("operator"); + uint256 assetToSharesRatio = 2; + + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + // Set approval + vm.prank(_alice); + vault.setApprovalForAll(operator, true); + + // Check if the operator is approved + bool isApproved = vault.isApprovedForAll(_alice, operator); + assertEq(isApproved, true, "Operator should be approved"); + + // Revoke approval and check + vm.prank(_alice); + vault.setApprovalForAll(operator, false); + isApproved = vault.isApprovedForAll(_alice, operator); + assertEq(isApproved, false, "Operator should not be approved"); + } + + function test__MultiTokenVaultTest__MaxDeposit() public { + uint256 assetToSharesRatio = 2; + + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + uint256 maxDepositValue = vault.maxDeposit(_alice); + assertEq(maxDepositValue, type(uint256).max, "Max deposit should be uint256 max"); + } + + function test__MultiTokenVaultTest__MaxRedeemAtPeriod() public { + uint256 assetToSharesRatio = 2; + + TestParamSet.TestParam memory testParams = + TestParamSet.TestParam({ principal: 500 * _scale, depositPeriod: 0, redeemPeriod: 30 }); + + IMultiTokenVault vault = _createMultiTokenVault(_asset, assetToSharesRatio, 10); + + // Alice deposits assets and receives shares + vm.startPrank(_alice); + _asset.approve(address(vault), testParams.principal); + uint256 shares = vault.deposit(testParams.principal, _alice); + vm.stopPrank(); + + // Check max redeemable shares for the deposit period + uint256 maxShares = vault.maxRedeemAtPeriod(_alice, testParams.depositPeriod); // Assuming deposit period = 1 + assertEq(maxShares, shares, "Max redeemable shares did not match the deposited shares"); + } + function _testBalanceOfBatch( address account, IMultiTokenVault vault, diff --git a/packages/contracts/test/test/token/ERC1155/MultiTokenVaultDailyPeriods.t.sol b/packages/contracts/test/test/token/ERC1155/MultiTokenVaultDailyPeriods.t.sol index 08d872aa..166138ed 100644 --- a/packages/contracts/test/test/token/ERC1155/MultiTokenVaultDailyPeriods.t.sol +++ b/packages/contracts/test/test/token/ERC1155/MultiTokenVaultDailyPeriods.t.sol @@ -9,8 +9,9 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils contract MultiTokenVaultDailyPeriods is Initializable, UUPSUpgradeable, MultiTokenVault { uint256 internal ASSET_TO_SHARES_RATIO; uint256 internal YIELD_PERCENTAGE; - uint256 public _currentPeriod; + uint256 private _currentPeriod; uint256 public constant SCALE = 1e6; + uint256 private _maxDeposit; function initialize(IERC20Metadata asset, uint256 assetToSharesRatio, uint256 yieldPercentage) public initializer { __MultiTokenVault_init(asset); @@ -61,4 +62,15 @@ contract MultiTokenVaultDailyPeriods is Initializable, UUPSUpgradeable, MultiTok function setCurrentPeriodsElapsed(uint256 currentTimePeriodsElapsed_) public { _currentPeriod = currentTimePeriodsElapsed_; } + + function maxDeposit(address forWhom_) public view virtual override returns (uint256) { + if (_maxDeposit != 0) { + return _maxDeposit; + } + return super.maxDeposit(forWhom_); + } + + function setMaxDeposit(uint256 maxDeposit_) public virtual returns (uint256) { + return _maxDeposit = maxDeposit_; + } }