Instantly share code, notes, and snippets.
Created
August 25, 2023 09:54
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save berndartmueller/932942d01a8e7d3c8e102eec9c8bbde0 to your computer and use it in GitHub Desktop.
Run with `forge test -vv --match-test "testFuzzCalcUserWithdrawSharesToBurn"`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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