Skip to content

Instantly share code, notes, and snippets.

@berndartmueller
Created August 25, 2023 09:54
Show Gist options
  • Save berndartmueller/932942d01a8e7d3c8e102eec9c8bbde0 to your computer and use it in GitHub Desktop.
Save berndartmueller/932942d01a8e7d3c8e102eec9c8bbde0 to your computer and use it in GitHub Desktop.
Run with `forge test -vv --match-test "testFuzzCalcUserWithdrawSharesToBurn"`
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
contract ContractTest is Test {
using Math for uint256;
struct DestinationInfo {
/// @notice Current underlying and reward value at the destination vault
/// @dev Used for calculating totalDebt of the LMPVault
uint256 currentDebt;
/// @notice Last block timestamp this info was updated
uint256 lastReport;
/// @notice How many shares of the destination vault we owned at last report
uint256 ownedShares;
/// @notice Amount of baseAsset transferred out in service of deployments
/// @dev Used for calculating 'in profit' or not during user withdrawals
uint256 debtBasis;
}
error WithdrawShareCalcInvalid(uint256 currentShares, uint256 cachedShares);
constructor() {}
function _calcUserWithdrawSharesToBurn(
uint256 currentDvShares,
uint256 currentDvDebtValue,
DestinationInfo memory destInfo,
uint256 maxAssetsToPull
) internal view returns (uint256 sharesToBurn, uint256 totalDebtBurn) {
// Figure out how many shares we can burn from the destination as well
// as what our totalDebt deduction should be (totalDebt being a cached value).
// If the destination vault is currently sitting at a profit, then the user can burn
// all the shares this vault owns. If its at a loss, they can only burn an amount
// proportional to their ownership of this vault. This is so a user doesn't lock in
// a loss for the entire vault during their withdrawal
// slither-disable-next-line incorrect-equality
if (currentDvShares == 0) {
return (0, 0);
}
// The amount of shares we had at the last debt reporting
uint256 cachedDvShares = destInfo.ownedShares;
// The value of our debt + earned rewards at last debt reporting
uint256 cachedCurrentDebt = destInfo.currentDebt;
// Our current share balance should only ever be lte the last snapshot
// Any update to the deployment should update the snapshot and withdrawals
// can only lower it
if (currentDvShares > cachedDvShares) {
revert WithdrawShareCalcInvalid(currentDvShares, cachedDvShares);
}
// Shouldn't pull more than we want
// Or, we're not in profit so we limit the pull
if (currentDvDebtValue < maxAssetsToPull) {
maxAssetsToPull = currentDvDebtValue;
}
// Calculate the portion of shares to burn based on the assets we need to pull
// and the current total debt value. These are destination vault shares.
sharesToBurn = currentDvShares.mulDiv(
maxAssetsToPull,
currentDvDebtValue,
Math.Rounding.Up
);
// This is what will be deducted from totalDebt with the withdrawal. The totalDebt number
// is calculated based on the cached values so we need to be sure to reduce it
// proportional to the original cached debt value
totalDebtBurn = cachedCurrentDebt.mulDiv(
sharesToBurn,
cachedDvShares,
Math.Rounding.Up
);
console.log("currentDvDebtValue: %e", currentDvDebtValue);
console.log("cachedCurrentDebt: %e", cachedCurrentDebt);
console.log("sharesToBurn: %e", sharesToBurn);
console.log("cachedDvShares: %e", cachedDvShares);
console.log("currentDvShares: %e", currentDvShares);
console.log("totalDebtBurn: %e", totalDebtBurn);
console.log("maxAssetsToPull: %e", maxAssetsToPull);
}
/**
* forge-config: default.fuzz.runs = 50240
*/
function testFuzzCalcUserWithdrawSharesToBurn(
uint256 currentDvShares,
uint256 ownedShares,
uint256 cachedCurrentDebt,
uint256 currentDvDebtValue,
uint256 totalAssetsToPull
) public {
cachedCurrentDebt = bound(cachedCurrentDebt, 1e18, 1e26);
currentDvDebtValue = bound(currentDvDebtValue, cachedCurrentDebt, 1e26);
ownedShares = bound(ownedShares, 1e18, 1e26);
currentDvShares = bound(currentDvShares, 0, ownedShares);
vm.assume(totalAssetsToPull > 0);
vm.assume(totalAssetsToPull >= currentDvDebtValue / 100);
DestinationInfo memory destInfo = DestinationInfo({
currentDebt: cachedCurrentDebt,
lastReport: 0, // not relevant
ownedShares: ownedShares,
debtBasis: 0 // not relevant
});
(
uint256 sharesToBurn
uint256 totalDebtBurn
) = _calcUserWithdrawSharesToBurn(
currentDvShares,
currentDvDebtValue,
destInfo,
totalAssetsToPull
);
if (totalDebtBurn > totalAssetsToPull) {
console.log("diff: %e", totalDebtBurn - totalAssetsToPull);
}
assertLe(totalDebtBurn, totalAssetsToPull);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment