Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LiquidStone to accrue yield until the requestRedeem period #168

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions packages/contracts/src/yield/LiquidContinuousMultiTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -374,15 +374,17 @@ contract LiquidContinuousMultiTokenVault is
_yieldStrategy = yieldStrategy;
}

/// @dev yield based on the associated yieldStrategy
function calcYield(uint256 principal, uint256 fromPeriod, uint256 toPeriod) public view returns (uint256 yield) {
// no yield earned when depositing and requesting redeem within the notice period.
// e.g. deposit day 1, immediately request redeem on day 1. should give 0 returns.
if (toPeriod <= fromPeriod + noticePeriod()) {
return 0;
}
/// @dev yield accrues up to the `requestRedeemPeriod` (as opposed to the `redeemPeriod`)
function calcYield(uint256 principal, uint256 depositPeriod, uint256 redeemPeriod)
public
view
returns (uint256 yield)
{
uint256 requestRedeemPeriod = redeemPeriod > noticePeriod() ? redeemPeriod - noticePeriod() : 0;

if (requestRedeemPeriod <= depositPeriod) return 0; // no yield when deposit and requestRedeems are the same period

return _yieldStrategy.calcYield(address(this), principal, fromPeriod, toPeriod);
return _yieldStrategy.calcYield(address(this), principal, depositPeriod, requestRedeemPeriod);
}

/// @dev price is not used in Vault calculations. however, 1 asset = 1 share, implying a price of 1
Expand Down
14 changes: 14 additions & 0 deletions packages/contracts/test/src/timelock/TimelockAsyncUnlockTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,20 @@ contract TimelockAsyncUnlockTest is Test {
);
}

function test__TimelockAsyncUnlock__RequestUnlockInvalidArrayLengthReverts() public {
uint256[] memory depositPeriods_ = new uint256[](2);
uint256[] memory amounts_ = new uint256[](1);

vm.expectRevert(
abi.encodeWithSelector(
TimelockAsyncUnlock.TimelockAsyncUnlock__InvalidArrayLength.selector,
depositPeriods_.length,
amounts_.length
)
);
asyncUnlock.requestUnlock(alice, depositPeriods_, amounts_);
}

function _asSingletonArray(uint256 element) internal pure returns (uint256[] memory array) {
array = new uint256[](1);
array[0] = element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ contract LiquidContinuousMultiTokenVaultTest is LiquidContinuousMultiTokenVaultT
LiquidContinuousMultiTokenVault liquidVault = _liquidVault; // _createLiquidContinueMultiTokenVault(_vaultParams);

TestParamSet.TestParam memory testParams =
TestParamSet.TestParam({ principal: 2_000 * _scale, depositPeriod: 10, redeemPeriod: 70 });
TestParamSet.TestParam({ principal: 2_000 * _scale, depositPeriod: 10, redeemPeriod: 71 });

uint256 assetStartBalance = _asset.balanceOf(alice);

Expand Down Expand Up @@ -283,13 +283,15 @@ contract LiquidContinuousMultiTokenVaultTest is LiquidContinuousMultiTokenVaultT
function test__LiquidContinuousMultiTokenVault__50k_Returns() public view {
uint256 deposit = 50_000 * _scale;

uint256 tenorPlusNoticePeriod = _liquidVault.TENOR() + _liquidVault.noticePeriod();

// verify returns
uint256 actualYield = _liquidVault.calcYield(deposit, 0, _liquidVault.TENOR());
uint256 actualYield = _liquidVault.calcYield(deposit, 0, tenorPlusNoticePeriod);
assertEq(416_666666, actualYield, "interest not correct for $50k deposit after 30 days");

// verify principal + returns
uint256 actualShares = _liquidVault.convertToShares(deposit);
uint256 actualReturns = _liquidVault.convertToAssetsForDepositPeriod(actualShares, 0, _liquidVault.TENOR());
uint256 actualReturns = _liquidVault.convertToAssetsForDepositPeriod(actualShares, 0, tenorPlusNoticePeriod);
assertEq(50_416_666666, actualReturns, "principal + interest not correct for $50k deposit after 30 days");
}

Expand Down Expand Up @@ -843,4 +845,57 @@ contract LiquidContinuousMultiTokenVaultTest is LiquidContinuousMultiTokenVaultT
uint256 assets = _liquidVault.redeem(shares, alice, alice);
assertEq(principal, assets, "assets should be the same as principal");
}

function test__LiquidContinuousMultiTokenVault__CalcYieldEdgeCases() public view {
uint256 principal = 1_000_000_000 * _scale;
uint256 zeroPeriod = 0;
uint256 hundredPeriod = 100;
uint256 noticePeriod = _liquidVault.noticePeriod();

// check scenarios with zero returns
assertEq(
0,
_liquidVault.calcYield(principal, zeroPeriod, zeroPeriod),
"no returns when redeeming at deposit period - deposit at 0"
);
assertEq(
0,
_liquidVault.calcYield(principal, hundredPeriod, hundredPeriod),
"no returns when redeeming at deposit period - deposit at 100"
);

assertEq(
0,
_liquidVault.calcYield(principal, zeroPeriod, zeroPeriod + noticePeriod),
"no returns when redeeming at notice period - deposit at 0"
);
assertEq(
0,
_liquidVault.calcYield(principal, hundredPeriod, hundredPeriod + noticePeriod),
"no returns when redeeming at notice period - deposit at 100"
);

assertEq(
0,
_liquidVault.calcYield(principal, 1, zeroPeriod),
"zero yield redeem less than deposit period - redeem at 0"
);
assertEq(
0,
_liquidVault.calcYield(principal, hundredPeriod, hundredPeriod - 1),
"zero yield redeem less than deposit period - redeem at 99"
);

// check scenarios with returns
assertLt(
0,
_liquidVault.calcYield(principal, zeroPeriod, zeroPeriod + noticePeriod + 1),
"redeem > notice period should have yield"
);
assertLt(
0,
_liquidVault.calcYield(principal, hundredPeriod, hundredPeriod + noticePeriod + 1),
"redeem > notice period should have yield"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,11 @@ abstract contract LiquidContinuousMultiTokenVaultTestBase is IMultiTokenVaultTes
{
LiquidContinuousMultiTokenVault liquidVault = LiquidContinuousMultiTokenVault(address(vault));

// LiquidStone stops accruing yield at the requestRedeem period
uint256 requestRedeemPeriod = testParam.redeemPeriod - _liquidVault.noticePeriod();

return liquidVault._yieldStrategy().calcYield(
address(vault), testParam.principal, testParam.depositPeriod, testParam.redeemPeriod
address(vault), testParam.principal, testParam.depositPeriod, requestRedeemPeriod
);
}

Expand Down
Loading