Skip to content

Commit

Permalink
VAL-111 Defaults only apply FL to cover the loan principal (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
ams9198 authored Dec 1, 2022
1 parent 1f2fbd0 commit 162155e
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 47 deletions.
6 changes: 1 addition & 5 deletions contracts/controllers/interfaces/IPoolController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ interface IPoolController {
/**
* @dev Emitted when first loss capital is used to cover loan defaults
*/
event FirstLossApplied(
address indexed loan,
uint256 amount,
uint256 outstandingLoss
);
event FirstLossApplied(address indexed loan, uint256 amount);

function admin() external view returns (address);

Expand Down
19 changes: 3 additions & 16 deletions contracts/libraries/PoolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@ library PoolLib {
/**
* @dev See IPool
*/
event FirstLossApplied(
address indexed loan,
uint256 amount,
uint256 outstandingLosses
);
event FirstLossApplied(address indexed loan, uint256 amount);

/**
* @dev See IPool for event definition
Expand Down Expand Up @@ -358,24 +354,15 @@ library PoolLib {
address(firstLossVault)
);

// TODO - handle open-term loans where principal may
// not be fully oustanding.
uint256 outstandingLoanDebt = ILoan(loan).outstandingPrincipal() +
ILoan(loan).paymentsRemaining() *
ILoan(loan).payment();

uint256 outstandingLoanDebt = ILoan(loan).outstandingPrincipal();
uint256 firstLossRequired = firstLossBalance >= outstandingLoanDebt
? outstandingLoanDebt
: firstLossBalance;

FirstLossVault(firstLossVault).withdraw(firstLossRequired, pool);

emit LoanDefaulted(loan);
emit FirstLossApplied(
loan,
firstLossRequired,
outstandingLoanDebt.sub(firstLossRequired)
);
emit FirstLossApplied(loan, firstLossRequired);
}

/*//////////////////////////////////////////////////////////////
Expand Down
74 changes: 61 additions & 13 deletions test/controllers/PoolController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ describe("PoolController", () => {
serviceConfiguration
);

const { loan: openTermLoan } = await deployLoan(
pool.address,
borrower.address,
liquidityAsset.address,
serviceConfiguration,
{ loanType: 1 }
);

return {
operator,
poolAdmin,
Expand All @@ -54,7 +62,8 @@ describe("PoolController", () => {
liquidityAsset,
collateralAsset,
poolController,
withdrawController
withdrawController,
openTermLoan
};
}

Expand Down Expand Up @@ -718,13 +727,7 @@ describe("PoolController", () => {
const outstandingLoanPrincipalsBefore = (await pool.accountings())
.outstandingLoanPrincipals;
const firstLossAvailable = await poolController.firstLossBalance();

// Expected loan outstanding stand = principal + numberPayments * payments
const loanPaymentsRemaining = await loan.paymentsRemaining();
const loanPaymentAmount = await loan.payment();
const loanOustandingDebt = loanPrincipal.add(
loanPaymentsRemaining.mul(loanPaymentAmount)
);
const loanOustandingDebt = loanPrincipal;

// Confirm that first loss is NOT enough to cover the outstanding loan debt
expect(firstLossAvailable).to.be.lessThan(loanOustandingDebt);
Expand All @@ -735,11 +738,7 @@ describe("PoolController", () => {
.to.emit(poolController, "LoanDefaulted")
.withArgs(loan.address)
.to.emit(poolController, "FirstLossApplied")
.withArgs(
loan.address,
firstLossAvailable,
loanOustandingDebt.sub(firstLossAvailable)
);
.withArgs(loan.address, firstLossAvailable);

// Check accountings after
// Pool accountings should be updated
Expand All @@ -756,6 +755,55 @@ describe("PoolController", () => {
);
});

it("defaults only supply first loss to cover outstanding loan principal", async () => {
const {
pool,
poolAdmin,
liquidityAsset,
openTermLoan,
borrower,
otherAccount,
poolController
} = await loadFixture(loadPoolFixture);
await activatePool(pool, poolAdmin, liquidityAsset);

// Deposit to pool and fund loan
await depositToPool(pool, otherAccount, liquidityAsset, 1_000_000);
await fundLoan(openTermLoan, poolController, poolAdmin);

await openTermLoan.connect(borrower).drawdown(500_000); // drawdown half

// Deposit enough FL to cover full loan principal
await liquidityAsset.mint(poolAdmin.address, 900_000);
await liquidityAsset
.connect(poolAdmin)
.approve(poolController.address, 900_000);
await poolController
.connect(poolAdmin)
.depositFirstLoss(900_000, poolAdmin.address);

expect(await poolController.firstLossBalance()).to.equal(1_000_000);

// Trigger default
const txn = await poolController
.connect(poolAdmin)
.defaultLoan(openTermLoan.address);

// Check that 500k moved from vault to pool
await expect(txn)
.to.changeTokenBalance(
liquidityAsset,
await pool.firstLossVault(),
-500_000
)
.to.changeTokenBalance(liquidityAsset, pool.address, +500_000);

await expect(txn).to.emit(poolController, "FirstLossApplied").withArgs(
openTermLoan.address,
500_000 // outstanding principal
);
});

it("Allows defaults even if pool is closed", async () => {
const {
collateralAsset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {
deployPool,
activatePool,
DEFAULT_POOL_SETTINGS
} from "../../../support/pool";
} from "../../support/pool";
import {
DEFAULT_LOAN_SETTINGS,
deployLoan,
fundLoan
} from "../../../support/loan";
import { deployMockERC20 } from "../../../support/erc20";
} from "../../support/loan";
import { deployMockERC20 } from "../../support/erc20";

describe("Fixed Term Defaulted Loan Scenario", () => {
const INPUTS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {
deployPool,
activatePool,
DEFAULT_POOL_SETTINGS
} from "../../../support/pool";
} from "../../support/pool";
import {
DEFAULT_LOAN_SETTINGS,
deployLoan,
fundLoan
} from "../../../support/loan";
import { deployMockERC20 } from "../../../support/erc20";
} from "../../support/loan";
import { deployMockERC20 } from "../../support/erc20";

describe("Fixed Term Matured Loan Scenario", () => {
const INPUTS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {
deployPool,
activatePool,
DEFAULT_POOL_SETTINGS
} from "../../../support/pool";
} from "../../support/pool";
import {
DEFAULT_LOAN_SETTINGS,
deployLoan,
fundLoan
} from "../../../support/loan";
import { deployMockERC20 } from "../../../support/erc20";
} from "../../support/loan";
import { deployMockERC20 } from "../../support/erc20";

describe("Open Term Defaulted Loan Scenario", () => {
const INPUTS = {
Expand Down Expand Up @@ -94,7 +94,9 @@ describe("Open Term Defaulted Loan Scenario", () => {

// default loan
await loan.connect(borrower).drawdown((await loan.principal()).div(2)); // drawdown half
await poolController.connect(poolAdmin).defaultLoan(loan.address);
const txn = await poolController
.connect(poolAdmin)
.defaultLoan(loan.address);

// check that outstanding principal goes down to 500k
expect((await pool.accountings()).outstandingLoanPrincipals).to.equal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {
deployPool,
activatePool,
DEFAULT_POOL_SETTINGS
} from "../../../support/pool";
} from "../../support/pool";
import {
DEFAULT_LOAN_SETTINGS,
deployLoan,
fundLoan
} from "../../../support/loan";
import { deployMockERC20 } from "../../../support/erc20";
} from "../../support/loan";
import { deployMockERC20 } from "../../support/erc20";

describe("Open Term Matured Loan Scenario", () => {
const INPUTS = {
Expand Down

0 comments on commit 162155e

Please sign in to comment.