Skip to content

Commit

Permalink
chore: Make internal audit changes (#22)
Browse files Browse the repository at this point in the history
* chore: start cleaning up debt locker

* feat: add README

* feat: fixed audit findings (#23)

* feat: fixed audit findings

* fix: PR comments

* formatting: update comments

Co-authored-by: Lucas <[email protected]>

Co-authored-by: Joao Gabriel Carvalho <[email protected]>
  • Loading branch information
Lucas Manuel and JGcarv authored Nov 11, 2021
1 parent 5f5e347 commit 62ea3da
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 11 deletions.
55 changes: 53 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
## Debt Locker Contracts
# DebtLocker

This readme describes what Collateral Lockers and their Factories are, and how they fit into the architecture.
[![CircleCI](https://circleci.com/gh/maple-labs/debt-locker/tree/main.svg?style=svg)](https://circleci.com/gh/maple-labs/debt-locker/tree/main) [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)

**DISCLAIMER: This code has NOT been externally audited and is actively being developed. Please do not use in production without taking the appropriate steps to ensure maximum security.**

DebtLocker is a smart contract that allows Pools to interact with different versions of Loans.

This contract has the following capabilities:
1. Claim funds from a loan, accounting for interest and principal respectively.
2. Accept terms of refinancing.
3. Perform repossession of funds and collateral from a Loan that is in default, transferring the funds to a Liquidator contract.
4. Set the allowed slippage and minimum price of collateral to be used by the liquidator contract.
4. Claim recovered funds from a liquidation, accounting for the amount that was recovered as principal in the context of the Pool, and registering the shortfall.

### Dependencies/Inheritance
The `DebtLocker` contract is deployed using the `MapleProxyFactory`, which can be found in the modules or on GitHub [here](https://github.com/maple-labs/maple-proxy-factory).

`MapleProxyFactory` inherits from the generic `ProxyFactory` contract which can be found [here](https://github.com/maple-labs/proxy-factory).

## Testing and Development
#### Setup
```sh
git clone [email protected]:maple-labs/debt-locker.git
cd debt-locker
dapp update
```
#### Running Tests
- To run all tests: `make test` (runs `./test.sh`)
- To run a specific test function: `./test.sh -t <test_name>` (e.g., `./test.sh -t test_setAllowedSlippage`)
- To run tests with a specified number of fuzz runs: `./test.sh -r <runs>` (e.g., `./test.sh -t test_setAllowedSlippage -r 10000`)

This project was built using [dapptools](https://github.com/dapphub/dapptools).

## Roles and Permissions
- **Governor**: Controls all implementation-related logic in the DebtLocker, allowing for new versions of proxies to be deployed from the same factory and upgrade paths between versions to be allowed.
- **Pool Delegate**: Can perform the following actions:
- Claim funds
- Set allowed slippage and minimum price for liquidations
- Trigger default
- Set the auctioneer (dictates price for liquidations) to another contract
- Accept refinance terms
- Set `fundsToCapture`, a variable that represents extra funds in the DebtLocker that should be sent to the Pool and registered as interest.

## About Maple
[Maple Finance](https://maple.finance) is a decentralized corporate credit market. Maple provides capital to institutional borrowers through globally accessible fixed-income yield opportunities.

For all technical documentation related to the currently deployed Maple protocol, please refer to the maple-core GitHub [wiki](https://github.com/maple-labs/maple-core/wiki).

---

<p align="center">
<img src="https://user-images.githubusercontent.com/44272939/116272804-33e78d00-a74f-11eb-97ab-77b7e13dc663.png" height="100" />
</p>
21 changes: 13 additions & 8 deletions contracts/DebtLocker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract DebtLocker is IDebtLocker, DebtLockerStorage, MapleProxied {
}

function upgrade(uint256 toVersion_, bytes calldata arguments_) external override {
require(msg.sender == IPoolLike(_pool).poolDelegate(), "DL:U:NOT_POOL_DELEGATE");
require(msg.sender == _getPoolDelegate(), "DL:U:NOT_POOL_DELEGATE");

IMapleProxyFactory(_factory()).upgradeInstance(toVersion_, arguments_);
}
Expand All @@ -53,6 +53,8 @@ contract DebtLocker is IDebtLocker, DebtLockerStorage, MapleProxied {
require(amount_ == uint256(0) || ERC20Helper.transfer(loan_.fundsAsset(), address(_loan), amount_));

loan_.acceptNewTerms(refinancer_, calls_, uint256(0));

_principalRemainingAtLastClaim = loan_.principal();
}

function setFundsToCapture(uint256 amount_) override external {
Expand Down Expand Up @@ -119,8 +121,6 @@ contract DebtLocker is IDebtLocker, DebtLockerStorage, MapleProxied {
);
}

// TODO: Add setters for allowed slippage and minRatio

/**********************/
/*** View Functions ***/
/**********************/
Expand Down Expand Up @@ -211,19 +211,24 @@ contract DebtLocker is IDebtLocker, DebtLockerStorage, MapleProxied {
require(!_isLiquidationActive(), "DL:HCOR:LIQ_NOT_FINISHED");

address fundsAsset = IMapleLoanLike(_loan).fundsAsset();
uint256 recoveredFunds = IERC20Like(fundsAsset).balanceOf(address(this)); // Funds recovered from liquidation and any unclaimed previous payment amounts
uint256 principalToCover = _principalRemainingAtLastClaim; // Principal remaining at time of liquidation
uint256 principalToCover = _principalRemainingAtLastClaim; // Principal remaining at time of liquidation
uint256 fundsCaptured = _fundsToCapture;

// Funds recovered from liquidation and any unclaimed previous payment amounts
uint256 recoveredFunds = IERC20Like(fundsAsset).balanceOf(address(this)) - fundsCaptured;

// If `recoveredFunds` is greater than `principalToCover`, the remaining amount is treated as interest in the context of the pool.
// If `recoveredFunds` is less than `principalToCover`, the difference is registered as a shortfall.
details_[0] = recoveredFunds;
details_[0] = recoveredFunds + fundsCaptured;
details_[1] = recoveredFunds > principalToCover ? recoveredFunds - principalToCover : 0;
details_[2] = fundsCaptured;
details_[5] = recoveredFunds > principalToCover ? principalToCover : recoveredFunds;
details_[6] = principalToCover > recoveredFunds ? principalToCover - recoveredFunds : 0;

require(ERC20Helper.transfer(fundsAsset, _pool, recoveredFunds), "DL:HCOR:TRANSFER");
require(ERC20Helper.transfer(fundsAsset, _pool, recoveredFunds + fundsCaptured), "DL:HCOR:TRANSFER");

_repossessed = false;
_fundsToCapture = uint256(0);
_repossessed = false;
}

function _handleClaim() internal returns (uint256[7] memory details_) {
Expand Down
38 changes: 37 additions & 1 deletion contracts/test/DebtLocker.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,8 @@ contract DebtLockerTest is TestUtils {

uint256 principalAfter = loan.principal();

assertEq(principalBefore + principalIncrease_, principalAfter);
assertEq(principalBefore + principalIncrease_, principalAfter);
assertEq(debtLocker.principalRemainingAtLastClaim(), principalAfter);
}

function test_fundsToCaptureForNextClaim() public {
Expand Down Expand Up @@ -590,4 +591,39 @@ contract DebtLockerTest is TestUtils {
pool.claim(address(debtLocker));
}

function test_fundsToCaptureWhileInDefault() public {
( loan, debtLocker ) = _createFundAndDrawdownLoan(1_000_000);

// Prepare additional amount to be captured
fundsAsset.mint(address(debtLocker), 500_000);

assertEq(fundsAsset.balanceOf(address(debtLocker)), 500_000);
assertEq(fundsAsset.balanceOf(address(pool)), 0);
assertEq(debtLocker.principalRemainingAtLastClaim(), loan.principalRequested());
assertEq(debtLocker.fundsToCapture(), 0);

// Trigger default
hevm.warp(loan.nextPaymentDueDate() + loan.gracePeriod() + 1);

pool.triggerDefault(address(debtLocker));

// After triggering default, set funds to capture
poolDelegate.debtLocker_setFundsToCapture(address(debtLocker), 500_000);

// Claim
uint256[7] memory details = pool.claim(address(debtLocker));

assertEq(fundsAsset.balanceOf(address(debtLocker)), 0);
assertEq(fundsAsset.balanceOf(address(pool)), 500_000);
assertEq(debtLocker.fundsToCapture(), 0);

assertEq(details[0], 500_000);
assertEq(details[1], 0);
assertEq(details[2], 500_000);
assertEq(details[3], 0);
assertEq(details[4], 0);
assertEq(details[5], 0);
assertEq(details[6], loan.principalRequested()); // No principal was recovered
}

}

0 comments on commit 62ea3da

Please sign in to comment.