Skip to content
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
4 changes: 2 additions & 2 deletions certora/specs/NoResidue.spec
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ rule supplyPreservesBalance(env e, BlueBundlesV1.MarketParams marketParams, uint
assert bundlerBalance[token] == before;
}

rule withdrawPreservesBalance(env e, BlueBundlesV1.MarketParams marketParams, uint256 assets, uint256 shares, uint256 minSharePriceE27, BlueBundlesV1.SignedAuthorization signedAuthorization, uint256 feePct, address recipient, address token, uint256 deadline) {
rule withdrawPreservesBalance(env e, BlueBundlesV1.MarketParams marketParams, uint256 assets, uint256 shares, BlueBundlesV1.SignedAuthorization signedAuthorization, uint256 feePct, address recipient, address token, uint256 deadline) {
require e.msg.sender != currentContract, "bundler is never its own caller";
require recipient != currentContract, "no bundler donations of the fee";

mathint before = bundlerBalance[token];
blueBundlesV1Withdraw(e, marketParams, assets, shares, minSharePriceE27, signedAuthorization, feePct, recipient, deadline);
blueBundlesV1Withdraw(e, marketParams, assets, shares, signedAuthorization, feePct, recipient, deadline);
assert bundlerBalance[token] == before;
}

Expand Down
11 changes: 4 additions & 7 deletions src/blue/BlueBundlesV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,11 @@ contract BlueBundlesV1 is IBlueBundlesV1, IMorphoRepayCallback {
/// @dev The referral fee is deducted from the withdrawn assets; the remainder is sent to msg.sender.
/// @dev Fee = withdrawnAssets * referralFeePct / WAD; net = withdrawnAssets - fee.
/// @dev To receive an amount W, pass assets = floor(W * WAD / (WAD - referralFeePct)).
/// @dev minSharePriceE27 lower-bounds the realized withdraw share price (withdrawn assets per share, scaled by 1e27).
Comment thread
QGarchery marked this conversation as resolved.
/// @dev The supply share price is not checked: any drop due to bad debt realisation is not quickly reversed, so a reverted exit retried later would be on similar or worse terms.
function blueBundlesV1Withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
uint256 minSharePriceE27,
SignedAuthorization memory signedAuthorization,
uint256 referralFeePct,
address referralFeeRecipient,
Expand All @@ -173,15 +172,13 @@ contract BlueBundlesV1 is IBlueBundlesV1, IMorphoRepayCallback {
require(referralFeePct < WAD, PctExceeded());

setAuthorizationWithSig(signedAuthorization);
(uint256 withdrawn, uint256 withdrawnShares) =
IMorpho(BLUE).withdraw(marketParams, assets, shares, msg.sender, address(this));
require(withdrawn.mulDivDown(1e27, withdrawnShares) >= minSharePriceE27, SlippageExceeded());
(assets,) = IMorpho(BLUE).withdraw(marketParams, assets, shares, msg.sender, address(this));

uint256 referralFeeAssets = withdrawn.mulDivDown(referralFeePct, WAD);
uint256 referralFeeAssets = assets.mulDivDown(referralFeePct, WAD);
if (referralFeeAssets > 0) {
SafeTransferLib.safeTransfer(marketParams.loanToken, referralFeeRecipient, referralFeeAssets);
}
SafeTransferLib.safeTransfer(marketParams.loanToken, msg.sender, withdrawn - referralFeeAssets);
SafeTransferLib.safeTransfer(marketParams.loanToken, msg.sender, assets - referralFeeAssets);
}

/// @dev Moves the full position of msg.sender (collateral and borrow shares, read from Blue) from the source market to the destination market.
Expand Down
1 change: 0 additions & 1 deletion src/blue/interfaces/IBlueBundlesV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ interface IBlueBundlesV1 {
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
uint256 minSharePriceE27,
SignedAuthorization memory signedAuthorization,
uint256 referralFeePct,
address referralFeeRecipient,
Expand Down
18 changes: 6 additions & 12 deletions src/vault/VaultBundlesV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,11 @@ contract VaultBundlesV1 is IVaultBundlesV1 {
/// @dev The referral fee is deducted from the withdrawn assets; the remainder is sent to msg.sender.
/// @dev Fee = withdrawnAssets * referralFeePct / WAD; net = withdrawnAssets - fee.
/// @dev To receive an amount W, pass assets = floor(W * WAD / (WAD - referralFeePct)).
/// @dev minSharePriceE27 lower-bounds the realized withdraw share price (withdrawn assets per share, scaled by 1e27).
/// @dev The vault share price is not checked: any drop (e.g. a bad debt realisation) is not quickly reversed, so a reverted exit retried later would be on similar or worse terms.
function vaultBundlesV1Withdraw(
address vault,
uint256 assets,
uint256 shares,
uint256 minSharePriceE27,
Permit memory sharesPermit,
uint256 referralFeePct,
address referralFeeRecipient,
Expand All @@ -80,9 +79,8 @@ contract VaultBundlesV1 is IVaultBundlesV1 {

TokenLib.submitPermit(vault, sharesPermit);

if (assets > 0) shares = IERC4626(vault).withdraw(assets, address(this), msg.sender);
if (assets > 0) IERC4626(vault).withdraw(assets, address(this), msg.sender);
else assets = IERC4626(vault).redeem(shares, address(this), msg.sender);
require(assets.mulDivDown(1e27, shares) >= minSharePriceE27, SlippageExceeded());

address asset = IERC4626(vault).asset();
uint256 referralFeeAssets = assets.mulDivDown(referralFeePct, WAD);
Expand All @@ -96,13 +94,13 @@ contract VaultBundlesV1 is IVaultBundlesV1 {
/// @dev Exactly one of assetsWithdrawn and sharesRedeemed should be non-zero: sourceVault is withdrawn by assets, or redeemed by shares. To migrate the sender's entire position, pass its full sourceVault share balance as shares.
/// @dev The referral fee is deducted from the withdrawn assets; the remainder is deposited into destVault.
/// @dev Fee = withdrawnAssets * referralFeePct / WAD; deposited = withdrawnAssets - fee.
/// @dev sourceMinSharePriceE27 lower-bounds the realized sourceVault withdraw share price; destMaxSharePriceE27 upper-bounds the realized destVault deposit share price (both assets per share, scaled by 1e27).
/// @dev The source vault share price is not checked: any drop (e.g. a bad debt realisation) is not quickly reversed, so a reverted exit retried later would be on similar or worse terms.
/// @dev destMaxSharePriceE27 upper-bounds the realized destVault deposit share price (deposited assets per share, scaled by 1e27).
function vaultBundlesV1Migrate(
address sourceVault,
address destVault,
uint256 assetsWithdrawn,
uint256 sharesRedeemed,
uint256 sourceMinSharePriceE27,
uint256 destMaxSharePriceE27,
Permit memory sharesPermit,
uint256 referralFeePct,
Expand All @@ -120,12 +118,8 @@ contract VaultBundlesV1 is IVaultBundlesV1 {
address asset = IERC4626(sourceVault).asset();
require(asset == IERC4626(destVault).asset(), InconsistentAssets());

if (assetsWithdrawn > 0) {
sharesRedeemed = IERC4626(sourceVault).withdraw(assetsWithdrawn, address(this), msg.sender);
} else {
assetsWithdrawn = IERC4626(sourceVault).redeem(sharesRedeemed, address(this), msg.sender);
}
require(assetsWithdrawn.mulDivDown(1e27, sharesRedeemed) >= sourceMinSharePriceE27, SlippageExceeded());
if (assetsWithdrawn > 0) IERC4626(sourceVault).withdraw(assetsWithdrawn, address(this), msg.sender);
else assetsWithdrawn = IERC4626(sourceVault).redeem(sharesRedeemed, address(this), msg.sender);

uint256 referralFeeAssets = assetsWithdrawn.mulDivDown(referralFeePct, WAD);
uint256 toDeposit = assetsWithdrawn - referralFeeAssets;
Expand Down
2 changes: 0 additions & 2 deletions src/vault/interfaces/IVaultBundlesV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ interface IVaultBundlesV1 {
address vault,
uint256 assets,
uint256 shares,
uint256 minSharePriceE27,
Permit memory sharesPermit,
uint256 referralFeePct,
address referralFeeRecipient,
Expand All @@ -43,7 +42,6 @@ interface IVaultBundlesV1 {
address destVault,
uint256 assetsWithdrawn,
uint256 sharesRedeemed,
uint256 sourceMinSharePriceE27,
uint256 destMaxSharePriceE27,
Permit memory sharesPermit,
uint256 referralFeePct,
Expand Down
31 changes: 7 additions & 24 deletions test/BlueBundlesTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ contract BlueBundlesTest is Test {
loanToken.approve(address(morpho), type(uint256).max);
morpho.supply(marketParams, assets, 0, sigUser, "");

blueBundles.blueBundlesV1Withdraw(marketParams, assets, 0, 0, authSig, 0, address(0), block.timestamp);
blueBundles.blueBundlesV1Withdraw(marketParams, assets, 0, authSig, 0, address(0), block.timestamp);
vm.stopPrank();

assertEq(loanToken.balanceOf(sigUser), assets);
Expand Down Expand Up @@ -422,7 +422,7 @@ contract BlueBundlesTest is Test {
marketParams, 1, type(uint256).max, _noPermit(), WAD, address(0), block.timestamp
);
vm.expectRevert(IBlueBundlesV1.PctExceeded.selector);
blueBundles.blueBundlesV1Withdraw(marketParams, 1, 0, 0, _noAuthSig(), WAD, address(0), block.timestamp);
blueBundles.blueBundlesV1Withdraw(marketParams, 1, 0, _noAuthSig(), WAD, address(0), block.timestamp);
vm.expectRevert(IBlueBundlesV1.PctExceeded.selector);
blueBundles.blueBundlesV1MigrateBorrowPosition(
marketParams, destMarketParams, type(uint256).max, 0, WAD, _noAuthSig(), WAD, address(0), block.timestamp
Expand All @@ -446,7 +446,7 @@ contract BlueBundlesTest is Test {
vm.expectRevert(IBlueBundlesV1.DeadlinePassed.selector);
blueBundles.blueBundlesV1Supply(marketParams, 1, type(uint256).max, _noPermit(), 0, address(0), past);
vm.expectRevert(IBlueBundlesV1.DeadlinePassed.selector);
blueBundles.blueBundlesV1Withdraw(marketParams, 1, 0, 0, _noAuthSig(), 0, address(0), past);
blueBundles.blueBundlesV1Withdraw(marketParams, 1, 0, _noAuthSig(), 0, address(0), past);
vm.expectRevert(IBlueBundlesV1.DeadlinePassed.selector);
blueBundles.blueBundlesV1MigrateBorrowPosition(
marketParams, destMarketParams, type(uint256).max, 0, WAD, _noAuthSig(), 0, address(0), past
Expand Down Expand Up @@ -925,9 +925,7 @@ contract BlueBundlesTest is Test {
vm.startPrank(user);
loanToken.approve(address(morpho), type(uint256).max);
morpho.supply(marketParams, supplyAssets, 0, user, "");
blueBundles.blueBundlesV1Withdraw(
marketParams, withdrawAssets, 0, 0, _noAuthSig(), 0, address(0), block.timestamp
);
blueBundles.blueBundlesV1Withdraw(marketParams, withdrawAssets, 0, _noAuthSig(), 0, address(0), block.timestamp);
vm.stopPrank();

assertEq(morpho.expectedSupplyAssets(marketParams, user), supplyAssets - withdrawAssets, "remaining supply");
Expand All @@ -947,7 +945,7 @@ contract BlueBundlesTest is Test {
loanToken.approve(address(morpho), type(uint256).max);
morpho.supply(marketParams, supplyAssets, 0, user, "");
blueBundles.blueBundlesV1Withdraw(
marketParams, withdrawAssets, 0, 0, _noAuthSig(), referralFeePct, referrer, block.timestamp
marketParams, withdrawAssets, 0, _noAuthSig(), referralFeePct, referrer, block.timestamp
);
vm.stopPrank();

Expand All @@ -974,7 +972,7 @@ contract BlueBundlesTest is Test {

vm.prank(user);
blueBundles.blueBundlesV1Withdraw(
marketParams, withdrawAssets, 0, 0, _noAuthSig(), referralFeePct, referrer, block.timestamp
marketParams, withdrawAssets, 0, _noAuthSig(), referralFeePct, referrer, block.timestamp
);

assertEq(loanToken.balanceOf(user), targetNet, "net equals target");
Expand All @@ -990,7 +988,7 @@ contract BlueBundlesTest is Test {
loanToken.approve(address(morpho), type(uint256).max);
morpho.supply(marketParams, supplyAssets, 0, user, "");
blueBundles.blueBundlesV1Withdraw(
marketParams, 0, morpho.supplyShares(id, user), 0, _noAuthSig(), referralFeePct, referrer, block.timestamp
marketParams, 0, morpho.supplyShares(id, user), _noAuthSig(), referralFeePct, referrer, block.timestamp
);
vm.stopPrank();

Expand Down Expand Up @@ -1252,21 +1250,6 @@ contract BlueBundlesTest is Test {
vm.stopPrank();
}

/// @dev A minSharePriceE27 above the realized withdraw share price reverts.
function testWithdrawSlippageExceeded() public {
uint256 supplyAssets = 100e18;
deal(address(loanToken), user, supplyAssets);

vm.startPrank(user);
loanToken.approve(address(morpho), type(uint256).max);
morpho.supply(marketParams, supplyAssets, 0, user, "");
vm.expectRevert(IBlueBundlesV1.SlippageExceeded.selector);
blueBundles.blueBundlesV1Withdraw(
marketParams, supplyAssets, 0, type(uint256).max, _noAuthSig(), 0, address(0), block.timestamp
);
vm.stopPrank();
}

/// @dev A minSharePriceE27 above the realized borrow share price reverts.
function testSupplyCollateralAndBorrowSlippageExceeded() public {
uint256 borrowAssets = 100e18;
Expand Down
Loading