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

Before token transfer blocking liquidations #402

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 0 additions & 16 deletions packages/protocol/src/abstracts/BaseVault.sol
Expand Up @@ -598,22 +598,6 @@ abstract contract BaseVault is ERC20, SystemAccessControl, PausableVault, VaultP
emit Withdraw(caller, receiver, owner, assets, shares);
}

/**
* @dev Hook before all token-share transfers.
* Requirements:
* - Must check `from` can move `amount` of shares.
*
* @param from address
* @param to address
* @param amount of shares
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal view override {
to;
if (from != address(0)) {
require(amount <= maxRedeem(from), "Transfer more than max");
}
}

/*//////////////////////////////////////////////////
Debt management: based on IERC4626 semantics
//////////////////////////////////////////////////*/
Expand Down
22 changes: 22 additions & 0 deletions packages/protocol/src/vaults/borrowing/BorrowingVault.sol
Expand Up @@ -59,6 +59,7 @@ contract BorrowingVault is BaseVault {
error BorrowingVault__borrow_moreThanAllowed();
error BorrowingVault__payback_invalidInput();
error BorrowingVault__payback_moreThanMax();
error BorrowingVault__beforeTokenTransfer_moreThanMax();
error BorrowingVault__liquidate_invalidInput();
error BorrowingVault__liquidate_positionHealthy();
error BorrowingVault__rebalance_invalidProvider();
Expand Down Expand Up @@ -150,6 +151,27 @@ contract BorrowingVault is BaseVault {
/// Debt management overrides ///
///////////////////////////////*/

/**
* @dev Hook before all asset-share transfers.
* Requirements:
* - Must check `from` can move `amount` of shares.
*
* @param from address
* @param to address
* @param amount of shares
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal view override {
/**
* @dev Hook check activated only when called by OZ {ERC20-_transfer}
* User must not be able to transfer asset-shares locked as collateral
*/
if (from != address(0) && to != address(0)) {
if (amount > maxRedeem(from)) {
revert BorrowingVault__beforeTokenTransfer_moreThanMax();
}
}
}

/// @inheritdoc IVault
function debtDecimals() public view override returns (uint8) {
return _debtDecimals;
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/vaults/yield/YieldVault.sol
Expand Up @@ -159,6 +159,7 @@ contract YieldVault is BaseVault {

/// @inheritdoc BaseVault
function _computeFreeAssets(address owner) internal view override returns (uint256) {
// There is no restriction on asset-share movements in a {YieldVault}.
return convertToAssets(balanceOf(owner));
}

Expand Down
35 changes: 34 additions & 1 deletion packages/protocol/test/mocking/vaults/Vault.t.sol
Expand Up @@ -181,11 +181,44 @@ contract VaultUnitTests is MockingSetup, MockRoutines {
do_depositAndBorrow(amount, borrowAmount, vault, ALICE);

vm.expectRevert(BaseVault.BaseVault__withdraw_moreThanMax.selector);

vm.prank(ALICE);
vault.withdraw(amount, ALICE, ALICE);
}

function test_tryTransferWithoutRepay(uint96 amount, uint96 borrowAmount) public {
uint256 minAmount = vault.minAmount();
vm.assume(
amount > minAmount && borrowAmount > minAmount && _utils_checkMaxLTV(amount, borrowAmount)
);
do_depositAndBorrow(amount, borrowAmount, vault, ALICE);

vm.expectRevert(BorrowingVault.BorrowingVault__beforeTokenTransfer_moreThanMax.selector);
vm.prank(ALICE);
vault.transfer(BOB, uint256(amount));
}

function test_tryTransferMaxRedeemWithoutRepay(uint96 amount, uint96 borrowAmount) public {
uint256 minAmount = vault.minAmount();
vm.assume(
amount > minAmount && borrowAmount > minAmount && _utils_checkMaxLTV(amount, borrowAmount)
);
do_depositAndBorrow(amount, borrowAmount, vault, ALICE);
uint256 maxTransferable = vault.maxRedeem(ALICE);

vm.prank(ALICE);
vault.transfer(BOB, maxTransferable);
assertEq(vault.balanceOf(BOB), maxTransferable);

uint256 nonTransferable = amount - maxTransferable;

vm.expectRevert(BorrowingVault.BorrowingVault__beforeTokenTransfer_moreThanMax.selector);
vm.prank(ALICE);
vault.transfer(BOB, nonTransferable);

// Bob's shares haven't change.
assertEq(vault.balanceOf(BOB), maxTransferable);
}

function test_setMinAmount(uint256 min) public {
vm.expectEmit(true, false, false, false);
emit MinAmountChanged(min);
Expand Down