Skip to content

Commit

Permalink
VAL-134 Add missing permissioning checks around the Loan (#175)
Browse files Browse the repository at this point in the history
* Add failing test: Borrower Permissioning should be applied when withdrawing collateral

* Add implementation: check borrower permissioning on collateral withdrawal.

* Mediate calls to Loan.reclaimFunds() through the PoolController, allowing protocol permissioning to be applied. Update existing tests + add new tests to cover this new exposed method -  PoolController.reclaimLoanFunds.

* Pipe claimCollateral calls from PA to a loan through the PoolController layer so global permissioning can be applied.

* Pipe cancel funded loan calls from PA to a loan through the PoolController layer so global permissioning can be applied.

* Pipe markCallback() calls through the PoolController layer

* Remove unused modifier

* Rename function for clarity

* Lint, edit natspec for clarity

* Create PermissionedPoolController test file, move relevant tests

* Lint fixes
  • Loading branch information
ams9198 authored Feb 14, 2023
1 parent f6cadab commit 29cd968
Show file tree
Hide file tree
Showing 11 changed files with 1,071 additions and 72 deletions.
68 changes: 45 additions & 23 deletions contracts/Loan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,6 @@ contract Loan is ILoan, BeaconImplementation {
_;
}

modifier onlyPoolAdmin() {
require(
msg.sender == IPool(_pool).admin(),
"Loan: caller is not pool admin"
);
_;
}

/**
* @dev Modifier that can be overriden by derived classes to enforce
* access control.
Expand Down Expand Up @@ -209,7 +201,8 @@ contract Loan is ILoan, BeaconImplementation {
returns (ILoanLifeCycleState)
{
require(
msg.sender == _borrower || msg.sender == IPool(_pool).admin(),
msg.sender == _borrower ||
msg.sender == address(IPool(_pool).poolController()),
"Loan: invalid caller"
);
require(
Expand All @@ -228,27 +221,56 @@ contract Loan is ILoan, BeaconImplementation {
}

/**
* @dev Claims specific collateral types. Can be called by the borrower
* (when Canceled or Matured) or by the PA (when Defaulted)
* @inheritdoc ILoan
*/
function claimCollateral(
address[] memory assets,
ILoanNonFungibleCollateral[] memory nonFungibleAssets
) external override onlyNotPaused {
address recipient;
if (msg.sender == _borrower) {
_checkBorrowerCanWithdrawCollateral();
recipient = _borrower;
} else {
// Only the PA or borrower can withdraw collateral.
_checkAdminCanWithdrawCollateral();
recipient = IPool(_pool).admin();
}

LoanLib.withdrawFungibleCollateral(collateralVault, assets, recipient);
LoanLib.withdrawNonFungibleCollateral(
collateralVault,
nonFungibleAssets,
recipient
);
}

/**
* @dev Internal check that a borrower is eligible to withdraw collateral.
*/
function _checkBorrowerCanWithdrawCollateral()
internal
view
onlyPermittedBorrower
{
require(
(_state == ILoanLifeCycleState.Canceled &&
msg.sender == _borrower) ||
(_state == ILoanLifeCycleState.Defaulted &&
msg.sender == IPool(_pool).admin()) ||
(_state == ILoanLifeCycleState.Matured &&
msg.sender == _borrower),
_state == ILoanLifeCycleState.Canceled ||
_state == ILoanLifeCycleState.Matured,
"Loan: unable to claim collateral"
);
}

LoanLib.withdrawFungibleCollateral(collateralVault, assets);
LoanLib.withdrawNonFungibleCollateral(
collateralVault,
nonFungibleAssets
/**
* @dev Internal check that a PA is eligible to withdraw collateral.
*/
function _checkAdminCanWithdrawCollateral()
internal
view
onlyPoolController
{
require(
_state == ILoanLifeCycleState.Defaulted,
"Loan: unable to claim collateral"
);
}

Expand Down Expand Up @@ -317,7 +339,7 @@ contract Loan is ILoan, BeaconImplementation {
/**
* @inheritdoc ILoan
*/
function reclaimFunds(uint256 amount) external onlyNotPaused onlyPoolAdmin {
function reclaimFunds(uint256 amount) external override onlyPoolController {
require(settings.loanType == ILoanType.Open);

fundingVault.withdrawERC20(liquidityAsset, amount, _pool);
Expand Down Expand Up @@ -508,7 +530,7 @@ contract Loan is ILoan, BeaconImplementation {
/**
* @inheritdoc ILoan
*/
function markCallback() external override onlyNotPaused onlyPoolAdmin {
function markCallback() external override onlyPoolController {
callbackTimestamp = block.timestamp;
}

Expand Down
54 changes: 54 additions & 0 deletions contracts/controllers/PoolController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,60 @@ contract PoolController is IPoolController, BeaconImplementation {
);
}

/**
* @inheritdoc IPoolController
*/
function reclaimLoanFunds(address loan, uint256 amount)
external
override
onlyNotPaused
onlyAdmin
onlyPermittedAdmin
onlySnapshottedPool
{
ILoan(loan).reclaimFunds(amount);
}

/**
* @inheritdoc IPoolController
* @dev Note that the Loan enforces the non-paused state, so it's omitted here.
*/
function claimLoanCollateral(
address loan,
address[] memory assets,
ILoanNonFungibleCollateral[] memory nonFungibleAssets
) external override onlyAdmin onlyPermittedAdmin onlySnapshottedPool {
ILoan(loan).claimCollateral(assets, nonFungibleAssets);
}

/**
* @inheritdoc IPoolController
* @dev Note that the Loan enforces the non-paused state, so it's omitted here.
*/
function cancelFundedLoan(address loan)
external
override
onlyAdmin
onlyPermittedAdmin
onlySnapshottedPool
{
ILoan(loan).cancelFunded();
}

/**
* @inheritdoc IPoolController
*/
function markLoanCallback(address loan)
external
override
onlyNotPaused
onlyAdmin
onlyPermittedAdmin
onlySnapshottedPool
{
ILoan(loan).markCallback();
}

/*//////////////////////////////////////////////////////////////
Fees
//////////////////////////////////////////////////////////////*/
Expand Down
28 changes: 28 additions & 0 deletions contracts/controllers/interfaces/IPoolController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.16;

import "../../interfaces/IPool.sol";
import "../../interfaces/ILoan.sol";

/**
* @dev Expresses the various states a pool can be in throughout its lifecycle.
Expand Down Expand Up @@ -214,6 +215,33 @@ interface IPoolController {
*/
function defaultLoan(address) external;

/**
* @dev Called by the pool admin, this allows reclaiming loan principal funds back to the pool
* from open-term loans.
*/
function reclaimLoanFunds(address loan, uint256 amount) external;

/**
* @dev Called by the pool admin, this allows claiming loan collateral
* back to the PA.
*/
function claimLoanCollateral(
address loan,
address[] memory assets,
ILoanNonFungibleCollateral[] memory nonFungibleAssets
) external;

/**
* @dev Called by the pool admin, this cancels a funded loan.
*/
function cancelFundedLoan(address loan) external;

/**
* @dev Called by the pool admin, this marks an open-term loan as being
* called back.
*/
function markLoanCallback(address loan) external;

/*//////////////////////////////////////////////////////////////
Fees
//////////////////////////////////////////////////////////////*/
Expand Down
10 changes: 6 additions & 4 deletions contracts/libraries/LoanLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,15 @@ library LoanLib {
*/
function withdrawFungibleCollateral(
IVault collateralVault,
address[] memory collateralToWithdraw
address[] memory collateralToWithdraw,
address recipient
) external {
for (uint256 i = 0; i < collateralToWithdraw.length; i++) {
address asset = collateralToWithdraw[i];

// Perform transfer
uint256 amount = IERC20(asset).balanceOf(address(collateralVault));
collateralVault.withdrawERC20(asset, amount, msg.sender);
collateralVault.withdrawERC20(asset, amount, recipient);
emit WithdrewCollateral(asset, amount);
}
}
Expand All @@ -171,14 +172,15 @@ library LoanLib {
*/
function withdrawNonFungibleCollateral(
IVault collateralVault,
ILoanNonFungibleCollateral[] memory collateralToWithdraw
ILoanNonFungibleCollateral[] memory collateralToWithdraw,
address recipient
) external {
for (uint256 i = 0; i < collateralToWithdraw.length; i++) {
ILoanNonFungibleCollateral memory wc = collateralToWithdraw[i];
address asset = wc.asset;
uint256 tokenId = wc.tokenId;

collateralVault.withdrawERC721(asset, tokenId, msg.sender);
collateralVault.withdrawERC721(asset, tokenId, recipient);
emit WithdrewNonFungibleCollateral(asset, tokenId);
}
}
Expand Down
Loading

0 comments on commit 29cd968

Please sign in to comment.