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

Add support for defaults #44

Merged
merged 7 commits into from
Oct 6, 2022
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
22 changes: 22 additions & 0 deletions contracts/Loan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import "./FundingVault.sol";
*/
contract Loan is ILoan {
IServiceConfiguration private immutable _serviceConfiguration;
address private immutable _factory;
ILoanLifeCycleState private _state = ILoanLifeCycleState.Requested;
address private immutable _borrower;
address private immutable _pool;
Expand Down Expand Up @@ -78,6 +79,7 @@ contract Loan is ILoan {

constructor(
IServiceConfiguration serviceConfiguration,
address factory,
address borrower,
address pool,
uint256 duration_,
Expand All @@ -89,6 +91,7 @@ contract Loan is ILoan {
uint256 dropDeadTimestamp
) {
_serviceConfiguration = serviceConfiguration;
_factory = factory;
_borrower = borrower;
_pool = pool;
_collateralVault = new CollateralVault(address(this));
Expand Down Expand Up @@ -231,6 +234,21 @@ contract Loan is ILoan {
return amount;
}

/**
* @inheritdoc ILoan
*/
function markDefaulted()
external
override
onlyPool
atState(ILoanLifeCycleState.Funded)
returns (ILoanLifeCycleState)
{
_state = ILoanLifeCycleState.Defaulted;
emit LifeCycleStateTransition(_state);
return _state;
}

function state() external view returns (ILoanLifeCycleState) {
return _state;
}
Expand All @@ -243,6 +261,10 @@ contract Loan is ILoan {
return _pool;
}

function factory() external view returns (address) {
return _factory;
}

function dropDeadTimestamp() external view returns (uint256) {
return _dropDeadTimestamp;
}
Expand Down
14 changes: 14 additions & 0 deletions contracts/LoanFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ contract LoanFactory {
*/
IServiceConfiguration private _serviceConfiguration;

/**
* @dev Mapping of created loans
*/
mapping(address => bool) private _isLoan;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling this one out -- I can see an argument for this to instead live on the ServiceConfiguration, perhaps in a larger mapping keyed off of the loanFactory address.

This would map loanFactoryAddress => createdLoanAddress => bool, or something similar.

This might be slightly more testable, but perhaps overloads the Config contract. Curious if anyone has thoughts!


/**
* @dev Emitted when a Loan is created.
*/
Expand Down Expand Up @@ -43,6 +48,7 @@ contract LoanFactory {
);
Loan loan = new Loan(
_serviceConfiguration,
address(this),
borrower,
pool,
duration,
Expand All @@ -55,6 +61,14 @@ contract LoanFactory {
);
address addr = address(loan);
emit LoanCreated(addr);
_isLoan[addr] = true;
return addr;
}

/**
* @dev Checks whether the address corresponds to a created loan for this factory
*/
function isLoan(address loan) public view returns (bool) {
return _isLoan[loan];
}
}
29 changes: 26 additions & 3 deletions contracts/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.16;

import "./interfaces/ILoan.sol";
import "./interfaces/IPool.sol";
import "./interfaces/IServiceConfiguration.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
Expand All @@ -19,6 +20,7 @@ contract Pool is IPool, ERC20 {

IPoolLifeCycleState private _poolLifeCycleState;
address private _manager;
IServiceConfiguration private _serviceConfiguration;
IERC20 private _liquidityAsset;
IPoolConfigurableSettings private _poolSettings;
FirstLossVault private _firstLossVault;
Expand Down Expand Up @@ -94,19 +96,22 @@ contract Pool is IPool, ERC20 {
* @param liquidityAsset asset held by the poo
* @param poolManager manager of the pool
* @param poolSettings configurable settings for the pool
* @param serviceConfiguration address of global service configuration
* @param tokenName Name used for issued pool tokens
* @param tokenSymbol Symbol used for issued pool tokens
*/
constructor(
address liquidityAsset,
address poolManager,
address serviceConfiguration,
IPoolConfigurableSettings memory poolSettings,
string memory tokenName,
string memory tokenSymbol
) ERC20(tokenName, tokenSymbol) {
_liquidityAsset = IERC20(liquidityAsset);
_poolSettings = poolSettings;
_manager = poolManager;
_serviceConfiguration = IServiceConfiguration(serviceConfiguration);
_firstLossVault = new FirstLossVault(address(this), liquidityAsset);
_setPoolLifeCycleState(IPoolLifeCycleState.Initialized);
}
Expand Down Expand Up @@ -221,13 +226,31 @@ contract Pool is IPool, ERC20 {

_liquidityAsset.safeApprove(address(loan), loan.principal());
loan.fund();
_accountings.activeLoanPrincipals += loan.principal();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be overwritten by the loan funding work, but the accounting in the default overflows without it, so I added it in

}

/**
* @dev Called by the pool manager, marks a loan as in default, updating pool accounting and allowing loan
* collateral to be claimed.
* @inheritdoc IPool
*/
function markLoanAsInDefault(address) external onlyManager {}
function defaultLoan(address loan)
external
onlyManager
atState(IPoolLifeCycleState.Active)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely correct, but I was torn whether we want it to be enforced, or leave it to the PM's discretion if something gets into a weird state (only way that it could would be a bug, but still).

{
require(loan != address(0), "Pool: 0 address");
require(
PoolLib.isPoolLoan(
loan,
address(_serviceConfiguration),
address(this)
),
"Pool: invalid loan"
);

ILoan(loan).markDefaulted();
_accountings.activeLoanPrincipals -= ILoan(loan).principal();
emit LoanDefaulted(loan);
}

/*//////////////////////////////////////////////////////////////
Withdrawal Request Methods
Expand Down
1 change: 1 addition & 0 deletions contracts/PoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ contract PoolFactory {
Pool pool = new Pool(
liquidityAsset,
msg.sender,
address(_serviceConfiguration),
settings,
"ValyriaPoolToken",
"VPT"
Expand Down
22 changes: 22 additions & 0 deletions contracts/ServiceConfiguration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration {

mapping(address => bool) public isLiquidityAsset;

/**
* @dev Holds a reference to valid LoanFactories
*/
mapping(address => bool) public isLoanFactory;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment re: LoanFactory mapping


/**
* @dev Emitted when an address is changed.
*/
Expand All @@ -36,6 +41,11 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration {
*/
event ProtocolPaused(bool paused);

/**
* @dev Emitted when a loan factory is set
*/
event LoanFactorySet(address indexed factory, bool isValid);

/**
* @dev Constructor for the contract, which sets up the default roles and
* owners.
Expand Down Expand Up @@ -78,4 +88,16 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration {
function isOperator(address addr) external view returns (bool) {
return hasRole(OPERATOR_ROLE, addr);
}

/**
* @inheritdoc IServiceConfiguration
*/
function setLoanFactory(address addr, bool isValid)
external
override
onlyOperator
{
isLoanFactory[addr] = isValid;
emit LoanFactorySet(addr, isValid);
}
}
9 changes: 9 additions & 0 deletions contracts/interfaces/ILoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ struct ILoanNonFungibleCollateral {
}

interface ILoan {
/**
* @dev Emitted when a Loan's lifecycle state transitions
*/
event LifeCycleStateTransition(ILoanLifeCycleState state);

/**
* @dev Emitted when collateral is posted to the loan.
*/
Expand All @@ -52,6 +57,8 @@ interface ILoan {

function pool() external view returns (address);

function factory() external view returns (address);

function dropDeadTimestamp() external view returns (uint256);

function cancelRequested() external returns (ILoanLifeCycleState);
Expand Down Expand Up @@ -90,4 +97,6 @@ interface ILoan {
function principal() external returns (uint256);

function fundingVault() external returns (FundingVault);

function markDefaulted() external returns (ILoanLifeCycleState);
}
6 changes: 3 additions & 3 deletions contracts/interfaces/IPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ interface IPool is IERC4626 {
function fundLoan(address) external;

/**
* @dev Called by the pool manager, marks a loan as in default, updating pool accounting and allowing loan
* collateral to be claimed.
* @dev Called by the pool manager, this marks a loan as in default, triggering liquiditation
* proceedings and updating pool accounting.
*/
function markLoanAsInDefault(address) external;
function defaultLoan(address) external;
}
14 changes: 14 additions & 0 deletions contracts/interfaces/IServiceConfiguration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,18 @@ interface IServiceConfiguration is IAccessControl {
function paused() external view returns (bool);

function isLiquidityAsset(address addr) external view returns (bool);

/**
* @dev checks if an address is a valid loan factory
* @param addr Address of loan factory
* @return bool whether the loan factory is valid
*/
function isLoanFactory(address addr) external view returns (bool);

/**
* @dev Sets whether a loan factory is valid
* @param addr Address of loan factory
* @param isValid Whether the loan factory is valid
*/
function setLoanFactory(address addr, bool isValid) external;
}
28 changes: 28 additions & 0 deletions contracts/libraries/PoolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../interfaces/ILoan.sol";
import "../interfaces/IPool.sol";
import "../interfaces/ILoan.sol";
import "../interfaces/IServiceConfiguration.sol";
import "../FirstLossVault.sol";
import "../LoanFactory.sol";

/**
* @title Collection of functions used by the Pool
Expand Down Expand Up @@ -45,6 +48,11 @@ library PoolLib {
uint256 shares
);

/**
* @dev See IPool for event definition
*/
event LoanDefaulted(address indexed loan);

/**
* @dev Transfers first loss to the vault.
* @param liquidityAsset Pool liquidity asset
Expand Down Expand Up @@ -237,4 +245,24 @@ library PoolLib {
) public view returns (uint256) {
return currentWithdrawPeriod(activatedAt, withdrawalWindowDuration) + 1;
}

/**
* @dev Determines whether an address corresponds to a pool loan
* @param loan address of loan
* @param serviceConfiguration address of service configuration
* @param pool address of pool
*/
function isPoolLoan(
address loan,
address serviceConfiguration,
address pool
) public view returns (bool) {
address factory = ILoan(loan).factory();
return
IServiceConfiguration(serviceConfiguration).isLoanFactory(
factory
) &&
LoanFactory(factory).isLoan(loan) &&
ILoan(loan).pool() == pool;
}
}
9 changes: 9 additions & 0 deletions contracts/mocks/PoolLibTestWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.16;

import "../libraries/PoolLib.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/IPool.sol";

/**
* @title PoolLibTestWrapper
Expand Down Expand Up @@ -119,4 +120,12 @@ contract PoolLibTestWrapper is ERC20("PoolLibTest", "PLT") {
_mint
);
}

function isPoolLoan(
address loan,
address serviceConfiguration,
address pool
) public view returns (bool) {
return PoolLib.isPoolLoan(loan, serviceConfiguration, pool);
}
}
12 changes: 11 additions & 1 deletion contracts/permissioned/PermissionedPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,20 @@ contract PermissionedPool is Pool {
constructor(
address liquidityAsset,
address poolManager,
address serviceConfiguration,
IPoolConfigurableSettings memory poolSettings,
string memory tokenName,
string memory tokenSymbol
) Pool(liquidityAsset, poolManager, poolSettings, tokenName, tokenSymbol) {
)
Pool(
liquidityAsset,
poolManager,
serviceConfiguration,
poolSettings,
tokenName,
tokenSymbol
)
{
_poolAccessControl = new PoolAccessControl(address(this));
}

Expand Down
3 changes: 2 additions & 1 deletion contracts/permissioned/PermissionedPoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ contract PermissionedPoolFactory is PoolFactory {
firstLossInitialMinimum,
withdrawRequestPeriodDuration
);
Pool pool = new PermissionedPool(
PermissionedPool pool = new PermissionedPool(
liquidityAsset,
msg.sender,
address(_serviceConfiguration),
settings,
"ValyriaPoolToken",
"VPT"
Expand Down
Loading