Skip to content

Commit

Permalink
Merge pull request #4 from La-DAO/feat/helper-for-ui
Browse files Browse the repository at this point in the history
Feat/helper for UI
  • Loading branch information
0xdcota authored Dec 8, 2024
2 parents c4a60d5 + 25e403b commit ca24bf7
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 131 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ make active decision on which users to lend based on different types of scoring

### Testnet Deployments

UICreditTalentHelper: [0xb99737fbf4252a521389d47256e4938e0f030ed1](https://sepolia.basescan.org/address/0xb99737fbf4252a521389d47256e4938e0f030ed1)

Xocolatl ERC20 address: [0x4eE906B7135bDBdfC83FE40b8f2156C99FCB64c2](https://sepolia.basescan.org/address/0x4eE906B7135bDBdfC83FE40b8f2156C99FCB64c2)
CreditTalentCenter (XOC): [0xBD03d38828Bf0D56f1d325F96d4d48d4a2fa3549](https://sepolia.basescan.org/address/0xBD03d38828Bf0D56f1d325F96d4d48d4a2fa3549)

Expand All @@ -14,7 +16,7 @@ CreditTalentCenter (USDC): [0x9A41029a07Ca57873CAd637384671349Fc9e8D9C](https://
Talent (mock) ERC20 address: [0xaAE22ccff30E636BDa436D54E5efea72227B2868](https://sepolia.basescan.org/address/0xaAE22ccff30E636BDa436D54E5efea72227B2868)
CreditTalentCenter (TAL): [0x465d5decA1A8d4c93e7D6a97018F0EFfCe56D247](https://sepolia.basescan.org/address/0x465d5decA1A8d4c93e7D6a97018F0EFfCe56D247)

CreditPoints Implementation: 0xa3ceD4b017F17Fd4ff5a4f1786b7bBF8F8067B31
CreditPoints Implementation: 0x83f6A967543510A7E0895c8d9618564CE60f1adb latest Dec-8-2024

Reference:

Expand Down
105 changes: 40 additions & 65 deletions src/CreditTalentCenter.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ICreditTalent, Application, ApplicationStatus, Underwriter} from "./interfaces/ICreditTalent.sol";
import {CreditPoints} from "./CreditPoints.sol";
import {FixedRateIrm} from "./FixedRateIrm.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
Expand All @@ -13,27 +14,7 @@ import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

enum ApplicationStatus {
None,
Pending,
Approved,
Rejected
}

struct Underwriter {
address underwriter;
uint256 approvalAmount;
}

struct Application {
uint256 id;
address applicant;
bytes32 dataHash;
address underwriter;
ApplicationStatus status;
}

contract CreditTalentCenter is IOracle, AccessControl {
contract CreditTalentCenter is ICreditTalent, IOracle, AccessControl {
using MarketParamsLib for MarketParams;
using SharesMathLib for uint256;
using SafeERC20 for IERC20;
Expand All @@ -43,24 +24,6 @@ contract CreditTalentCenter is IOracle, AccessControl {
uint256 public constant DEFAULT_LLTV = 0.98e18;
uint256 public constant FLOATING_RATE = type(uint256).max;

/// Events
event ApplicationCreated(uint256 id, address indexed applicant, bytes32 dataHash);
event ApplicationApproved(
uint256 id, address indexed applicant, address indexed underwriter, uint256 amount, uint256 interestRate
);
event ApplicationRejected(uint256 id, address indexed applicant, address indexed underwriter, string reason);
event UnderwriterSet(address indexed account, uint256 approvalPower);
event FixedRateIrmSet(uint256 indexed interestRate, address irm);

/// Custom errors
error CrediTalentCenter_zeroAddress();
error CrediTalentCenter_applicationAlreadyExists();
error CrediTalentCenter_fixedRateIrmAlreadyExists();
error CreditTalentCenter_invalidApplicationId();
error CreditTalentCenter_applicationNotPending();
error CreditTalentCenter_insufficientUnderwritingPower();
error CreditTalentCenter_invalidInterestRate();

address public immutable underwritingAsset;
address public immutable creditPoints;
IMorpho public immutable morpho;
Expand All @@ -72,7 +35,7 @@ contract CreditTalentCenter is IOracle, AccessControl {
mapping(address => uint256) public creditShares;
mapping(uint256 => FixedRateIrm) public fixedRateIrms; // InterestRate (in WAD) => IIrm address
mapping(address => Underwriter) public underwriters;
mapping(address => Application) public applicationInfo; // User address => Application
mapping(address => Application) internal _applicationInfo; // User address => Application

constructor(address underwritingAsset_, CreditPoints creditPointsImpl_, IMorpho morpho_, address adaptiveIrm_) {
_checkZeroAddress(underwritingAsset_);
Expand All @@ -99,6 +62,28 @@ contract CreditTalentCenter is IOracle, AccessControl {
}

/// View functions
function applicationInfo(address user_)
external
view
override
returns (
uint256 id,
address applicant,
bytes32 dataHash,
address underwriter,
ApplicationStatus status,
address irm
)
{
return (
_applicationInfo[user_].id,
_applicationInfo[user_].applicant,
_applicationInfo[user_].dataHash,
_applicationInfo[user_].underwriter,
_applicationInfo[user_].status,
_applicationInfo[user_].irm
);
}

/// @inheritdoc IOracle
function price() external view returns (uint256) {
Expand All @@ -107,20 +92,6 @@ contract CreditTalentCenter is IOracle, AccessControl {
return scaleFactor * 10 ** underwriteAssetDecimals;
}

function getUserLoanInfo(address user_) external view returns (uint256 creditLine, uint256 borrowed) {
Application memory application = applicationInfo[user_];
if (application.underwriter == address(0)) {
return (0, 0);
}
creditLine = creditShares[application.underwriter];

MarketParams memory marketParams =
MarketParams(underwritingAsset, creditPoints, address(this), adpativeIrm, DEFAULT_LLTV);
Position memory morphoPosition = morpho.position(marketParams.id(), user_);
Market memory market = morpho.market(marketParams.id());
borrowed = uint256(morphoPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares);
}

/// Core functions

/**
Expand All @@ -129,9 +100,10 @@ contract CreditTalentCenter is IOracle, AccessControl {
*/
function applyToCredit(bytes32 dataHash_) public {
// TODO: Add signature verification
require(applicationInfo[msg.sender].applicant == address(0), CrediTalentCenter_applicationAlreadyExists());
require(_applicationInfo[msg.sender].applicant == address(0), CrediTalentCenter_applicationAlreadyExists());
uint256 id = _useApplicationNumber();
applicationInfo[msg.sender] = Application(id, msg.sender, dataHash_, address(0), ApplicationStatus.Pending);
_applicationInfo[msg.sender] =
Application(id, msg.sender, dataHash_, address(0), ApplicationStatus.Pending, address(0));
emit ApplicationCreated(id, msg.sender, dataHash_);
}

Expand All @@ -141,7 +113,7 @@ contract CreditTalentCenter is IOracle, AccessControl {
*/
function applyToUnderwrite(uint256 amount_) external {
SafeERC20.safeTransferFrom(IERC20(underwritingAsset), msg.sender, address(this), amount_);
underwriters[msg.sender] = Underwriter(msg.sender, amount_);
underwriters[msg.sender] = Underwriter(msg.sender, amount_, new address[](0));
_grantRole(UNDERWRITER_ROLE, msg.sender);
CreditPoints(creditPoints).mint(address(this), amount_);
emit UnderwriterSet(msg.sender, amount_);
Expand All @@ -152,14 +124,14 @@ contract CreditTalentCenter is IOracle, AccessControl {
* @param user_ User address
* @param applicationId_ Application ID
* @param amount_ Amount of credit to approve
* @param iRateWad_ pass 0 for adaptive interest rate, or interest rate in WAD for fixed rate
* @param iRateWad_ pass type(uint256).max for adaptive interest rate, or interest rate in WAD for fixed rate
*/
function approveCredit(address user_, uint256 applicationId_, uint256 amount_, uint256 iRateWad_)
external
onlyRole(UNDERWRITER_ROLE)
{
require(applicationInfo[user_].id == applicationId_, CreditTalentCenter_invalidApplicationId());
require(applicationInfo[user_].status == ApplicationStatus.Pending, CreditTalentCenter_applicationNotPending());
require(_applicationInfo[user_].id == applicationId_, CreditTalentCenter_invalidApplicationId());
require(_applicationInfo[user_].status == ApplicationStatus.Pending, CreditTalentCenter_applicationNotPending());
require(underwriters[msg.sender].approvalAmount >= amount_, CreditTalentCenter_insufficientUnderwritingPower());

address rateModel = iRateWad_ == FLOATING_RATE ? adpativeIrm : address(fixedRateIrms[iRateWad_]);
Expand All @@ -169,8 +141,11 @@ contract CreditTalentCenter is IOracle, AccessControl {
creditShares[msg.sender] += amount_;
totalcreditShares += amount_;

applicationInfo[user_].status = ApplicationStatus.Approved;
applicationInfo[user_].underwriter = msg.sender;
_applicationInfo[user_].status = ApplicationStatus.Approved;
_applicationInfo[user_].underwriter = msg.sender;
_applicationInfo[user_].irm = rateModel;

underwriters[msg.sender].approvedApplicants.push(user_);

MarketParams memory marketParams =
MarketParams(underwritingAsset, creditPoints, address(this), rateModel, DEFAULT_LLTV);
Expand All @@ -185,9 +160,9 @@ contract CreditTalentCenter is IOracle, AccessControl {
external
onlyRole(UNDERWRITER_ROLE)
{
require(applicationInfo[user_].id == applicationId_, CreditTalentCenter_invalidApplicationId());
require(applicationInfo[user_].status == ApplicationStatus.Pending, CreditTalentCenter_applicationNotPending());
applicationInfo[user_].status = ApplicationStatus.Rejected;
require(_applicationInfo[user_].id == applicationId_, CreditTalentCenter_invalidApplicationId());
require(_applicationInfo[user_].status == ApplicationStatus.Pending, CreditTalentCenter_applicationNotPending());
_applicationInfo[user_].status = ApplicationStatus.Rejected;
emit ApplicationRejected(applicationId_, user_, msg.sender, reason_);
}

Expand Down
57 changes: 57 additions & 0 deletions src/UICreditTalentHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Application, ApplicationStatus, CreditTalentCenter} from "./CreditTalentCenter.sol";
import {IMorpho, Id, MarketParams, Market, Position} from "@morpho/contracts/interfaces/IMorpho.sol";
import {MarketParamsLib} from "@morpho/contracts/libraries/MarketParamsLib.sol";
import {SharesMathLib} from "@morpho/contracts/libraries/SharesMathLib.sol";
import {IIrm} from "@morpho/contracts/interfaces/IIrm.sol";

contract UICreditTalentHelper {
using MarketParamsLib for MarketParams;
using SharesMathLib for uint256;

// Number of seconds in a year (365 days)
uint256 private constant SECONDS_PER_YEAR = 365 days;

// Scaling factors
uint256 private constant SCALING_FACTOR = 1e18;
uint256 private constant RAY = 1e27;

/**
* @notice Get user loan information
* @param user from whom to get loan information
* @return creditLine amount approved by underwriter to user
* @return debtBalance borrowed + accrued interest amount by user currently
* @return interestRatePerSecond scaled 1e18 (see https://docs.morpho.org/morpho/contracts/irm/#borrow-apy to convert to APY)
*/
function getUserLoanInfo(address creditCenter, address user)
external
view
returns (uint256 creditLine, uint256 debtBalance, uint256 interestRatePerSecond)
{
CreditTalentCenter creditTalent = CreditTalentCenter(creditCenter);
Application memory application;
(,,, application.underwriter, application.status, application.irm) = creditTalent.applicationInfo(user);
if (application.underwriter == address(0) || application.status != ApplicationStatus.Approved) {
return (0, 0, 0);
}
creditLine = creditTalent.creditShares(application.underwriter);
address irm = application.irm == address(0) ? creditTalent.adpativeIrm() : application.irm;

IMorpho morpho = creditTalent.morpho();

MarketParams memory marketParams = MarketParams(
creditTalent.underwritingAsset(),
creditTalent.creditPoints(),
address(creditTalent),
irm,
creditTalent.DEFAULT_LLTV()
);
Position memory morphoPosition = morpho.position(marketParams.id(), user);
Market memory market = morpho.market(marketParams.id());
debtBalance =
uint256(morphoPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares);
interestRatePerSecond = IIrm(irm).borrowRateView(marketParams, market);
}
}
65 changes: 65 additions & 0 deletions src/interfaces/ICreditTalent.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IMorpho} from "@morpho/contracts/interfaces/IMorpho.sol";

enum ApplicationStatus {
None,
Pending,
Approved,
Rejected
}

struct Underwriter {
address underwriter;
uint256 approvalAmount;
address[] approvedApplicants;
}

struct Application {
uint256 id;
address applicant;
bytes32 dataHash;
address underwriter;
ApplicationStatus status;
address irm;
}

interface ICreditTalent {
/// Events
event ApplicationCreated(uint256 id, address indexed applicant, bytes32 dataHash);
event ApplicationApproved(
uint256 id, address indexed applicant, address indexed underwriter, uint256 amount, uint256 interestRate
);
event ApplicationRejected(uint256 id, address indexed applicant, address indexed underwriter, string reason);
event UnderwriterSet(address indexed account, uint256 approvalPower);
event FixedRateIrmSet(uint256 indexed interestRate, address irm);

/// Custom errors
error CrediTalentCenter_zeroAddress();
error CrediTalentCenter_applicationAlreadyExists();
error CrediTalentCenter_fixedRateIrmAlreadyExists();
error CreditTalentCenter_invalidApplicationId();
error CreditTalentCenter_applicationNotPending();
error CreditTalentCenter_insufficientUnderwritingPower();
error CreditTalentCenter_invalidInterestRate();

/// View
function underwritingAsset() external view returns (address);
function creditPoints() external view returns (address);
function DEFAULT_LLTV() external view returns (uint256);
function adpativeIrm() external view returns (address);
function morpho() external view returns (IMorpho);
function applicationInfo(address user)
external
view
returns (
uint256 id,
address applicant,
bytes32 dataHash,
address underwriter,
ApplicationStatus status,
address irm
);
function creditShares(address underwriter) external view returns (uint256);
}
Loading

0 comments on commit ca24bf7

Please sign in to comment.