Skip to content

Commit

Permalink
Merge pull request #121 from credbull/redeemer-strategy
Browse files Browse the repository at this point in the history
Improve the `RedeemOptimizerFIFO` loops.
  • Loading branch information
jplodge-pro authored Oct 5, 2024
2 parents f8946a2 + 6ba665e commit df5456f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 38 deletions.
71 changes: 40 additions & 31 deletions packages/contracts/src/token/ERC1155/RedeemOptimizerFIFO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,52 +67,37 @@ contract RedeemOptimizerFIFO is IRedeemOptimizer {
revert RedeemOptimizer__InvalidDepositPeriodRange(fromDepositPeriod, toDepositPeriod);
}

uint256 currentPeriod = vault.currentPeriodsElapsed();
if (toDepositPeriod > currentPeriod) {
revert RedeemOptimizer__FutureToDepositPeriod(toDepositPeriod, currentPeriod);
if (toDepositPeriod > vault.currentPeriodsElapsed()) {
revert RedeemOptimizer__FutureToDepositPeriod(toDepositPeriod, vault.currentPeriodsElapsed());
}

// first loop: check for periods with balances. needed to correctly size our array results
uint256 numPeriodsWithBalance = 0;
for (uint256 depositPeriod = fromDepositPeriod; depositPeriod <= toDepositPeriod; ++depositPeriod) {
uint256 sharesAtPeriod = vault.balanceOf(owner, depositPeriod);

uint256 amountAtPeriod = amountType == AmountType.Shares
? sharesAtPeriod
: vault.convertToAssetsForDepositPeriod(sharesAtPeriod, depositPeriod, redeemPeriod);

if (amountAtPeriod > 0) {
numPeriodsWithBalance++;
}
}

// second loop - collect and return the periods and amounts
depositPeriods = new uint256[](numPeriodsWithBalance);
amountAtPeriods = new uint256[](numPeriodsWithBalance);

// Create local caching arrays that can contain the maximum number of results.
uint256[] memory cacheDepositPeriods = new uint256[]((toDepositPeriod - fromDepositPeriod) + 1);
uint256[] memory cacheAmountAtPeriods = new uint256[]((toDepositPeriod - fromDepositPeriod) + 1);
uint256 arrayIndex = 0;
uint256 amountFound = 0;

// Iterate over the from/to period range, inclusive of from and to.
for (uint256 depositPeriod = fromDepositPeriod; depositPeriod <= toDepositPeriod; ++depositPeriod) {
uint256 sharesAtPeriod = vault.balanceOf(owner, depositPeriod);

uint256 amountAtPeriod = amountType == AmountType.Shares
? sharesAtPeriod
: vault.convertToAssetsForDepositPeriod(sharesAtPeriod, depositPeriod, redeemPeriod);

// If there is an Amount, store the value.
if (amountAtPeriod > 0) {
depositPeriods[arrayIndex] = depositPeriod;

// check if we will go "over" the amountToFind
if ((amountFound + amountAtPeriod) > amountToFind) {
amountAtPeriods[arrayIndex] = amountToFind - amountFound; // include only the amount up to amountToFind
cacheDepositPeriods[arrayIndex] = depositPeriod;

return (depositPeriods, amountAtPeriods); // we're done, no need to keep looping
// check if we will go "over" the Amount To Find.
if (amountFound + amountAtPeriod > amountToFind) {
cacheAmountAtPeriods[arrayIndex] = amountToFind - amountFound; // include only the partial amount
amountFound += cacheAmountAtPeriods[arrayIndex++];
break;
} else {
amountAtPeriods[arrayIndex] = amountAtPeriod;
cacheAmountAtPeriods[arrayIndex] = amountAtPeriod;
}

amountFound += amountAtPeriods[arrayIndex];
amountFound += cacheAmountAtPeriods[arrayIndex];
arrayIndex++;
}
}
Expand All @@ -121,6 +106,30 @@ contract RedeemOptimizerFIFO is IRedeemOptimizer {
revert RedeemOptimizer__OptimizerFailed(amountFound, amountToFind);
}

return (depositPeriods, amountAtPeriods);
return _trimToSize(arrayIndex, cacheDepositPeriods, cacheAmountAtPeriods);
}

/**
* @notice Utility function that trims the specified arrays to the specified size.
* @dev Allocates 2 arrays of size `toSize` and copies the `array1` and `array2` elements to their corresponding
* trimmed version. Assumes that the parameter arrays are at least as large as `toSize`.
*
* @param toSize The size to trim the arrays to.
* @param toTrim1 The first array to trim.
* @param toTrim2 The second array to trim.
* @return trimmed1 The trimmed version of `array1`.
* @return trimmed2 The trimmed version of `array2`.
*/
function _trimToSize(uint256 toSize, uint256[] memory toTrim1, uint256[] memory toTrim2)
private
pure
returns (uint256[] memory trimmed1, uint256[] memory trimmed2)
{
trimmed1 = new uint256[](toSize);
trimmed2 = new uint256[](toSize);
for (uint256 i = 0; i < toSize; i++) {
trimmed1[i] = toTrim1[i];
trimmed2[i] = toTrim2[i];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract RedeemOptimizerTest is MultiTokenVaultTest {
(uint256[] memory depositPeriods, uint256[] memory depositShares) = _testDeposits(alice, multiTokenVault); // make a few deposits
uint256 totalDepositShares = depositShares[0] + depositShares[1] + depositShares[2];

// warp vault ahead redemPeriod
// warp vault ahead redeemPeriod
uint256 redeemPeriod = deposit3TestParams.redeemPeriod;
_warpToPeriod(multiTokenVault, redeemPeriod);

Expand All @@ -34,24 +34,31 @@ contract RedeemOptimizerTest is MultiTokenVaultTest {
assertEq(3, redeemDepositPeriods.length, "depositPeriods wrong length - full redeem");
assertEq(3, sharesAtPeriods.length, "sharesAtPeriods wrong length - full redeem");

assertEq(deposit1TestParams.depositPeriod, redeemDepositPeriods[0], "optimizeRedeem - wrong depositPeriod");
assertEq(depositShares[0], sharesAtPeriods[0], "optimizeRedeem - wrong shares");
assertEq(deposit1TestParams.depositPeriod, redeemDepositPeriods[0], "optimizeRedeem - wrong depositPeriod 0");
assertEq(depositShares[0], sharesAtPeriods[0], "optimizeRedeem - wrong shares 0");

// TODO - check the other depositPeriods
assertEq(deposit2TestParams.depositPeriod, redeemDepositPeriods[1], "optimizeRedeem - wrong depositPeriod 1");
assertEq(depositShares[1], sharesAtPeriods[1], "optimizeRedeem - wrong shares 1");

assertEq(deposit3TestParams.depositPeriod, redeemDepositPeriods[2], "optimizeRedeem - wrong depositPeriod 2");
assertEq(depositShares[2], sharesAtPeriods[2], "optimizeRedeem - wrong shares 2");

// Check full withdraw
uint256[] memory expectedAssetsAtPeriods =
multiTokenVault.convertToAssetsForDepositPeriods(depositShares, depositPeriods, redeemPeriod);

(uint256[] memory withdrawDepositPeriods, uint256[] memory actualAssetsAtPeriods) =
redeemOptimizer.optimizeWithdrawAssets(multiTokenVault, alice, totalDepositShares, redeemPeriod);

assertEq(3, withdrawDepositPeriods.length, "depositPeriods wrong length - full redeem");
assertEq(3, actualAssetsAtPeriods.length, "sharesAtPeriods wrong length - full redeem");
assertEq(2, withdrawDepositPeriods.length, "depositPeriods wrong length - full redeem");
assertEq(2, actualAssetsAtPeriods.length, "sharesAtPeriods wrong length - full redeem");

assertEq(deposit1TestParams.depositPeriod, withdrawDepositPeriods[0], "optimizeWithdraw - wrong depositPeriod");
assertEq(expectedAssetsAtPeriods[0], actualAssetsAtPeriods[0], "optimizeWithdraw - wrong assets");

// TODO - check the other withdrawPeriods
// This is a partial amount, as the 2 amounts satisfy the 'to find' criterion.
assertEq(deposit2TestParams.depositPeriod, withdrawDepositPeriods[1], "optimizeWithdraw - wrong depositPeriod");
assertEq(200 * SCALE, actualAssetsAtPeriods[1], "optimizeWithdraw - wrong partial assets");
}

function test__RedeemOptimizerTest__InsufficientSharesShouldRevert() public {
Expand Down

0 comments on commit df5456f

Please sign in to comment.