Skip to content

Commit

Permalink
feat: initial setup credit talent
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdcota committed Oct 7, 2024
0 parents commit dfb38f7
Show file tree
Hide file tree
Showing 15 changed files with 420 additions and 0 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Show Forge version
run: |
forge --version
- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
forge build --sizes
id: build

# - name: Run Forge tests
# run: |
# forge test -vvv
# id: test
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
18 changes: 18 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/morpho"]
path = lib/morpho
url = https://github.com/morpho-org/morpho-blue
[submodule "lib/morpho-irm"]
path = lib/morpho-irm
url = https://github.com/morpho-org/morpho-blue-irm
[submodule "lib/morpho-oracles"]
path = lib/morpho-oracles
url = https://github.com/morpho-org/morpho-blue-oracles
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
6 changes: 6 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 8f24d6
1 change: 1 addition & 0 deletions lib/morpho
Submodule morpho added at 55d2d9
1 change: 1 addition & 0 deletions lib/morpho-irm
Submodule morpho-irm added at ef5dcc
1 change: 1 addition & 0 deletions lib/morpho-oracles
Submodule morpho-oracles added at 07a9a6
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at dbb610
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
3 changes: 3 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@openzeppelin-contracts/contracts/=lib/openzeppelin-contracts/contracts/
@openzepplein-upgradeable/contracts=lib/openzeppelin-contracts-upgradeable/contracts/
@morpho/contracts/=lib/morpho/src/
91 changes: 91 additions & 0 deletions src/CrediPoints.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ERC20PermitUpgradeable} from
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

struct CrediPointsStorage {
uint8 decimals;
address underWritingAsset;
mapping(address => bool) approvedReceivers;
}

/// @custom:security-contact [email protected]
contract CrediPoints is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable {
/// Events
event ApprovedTransactorSet(address indexed transactor, bool approved);

/// Custom errors
error CreditPoints_zeroAddress();
error CreditPoints_zeroAmount();

// cast keccak CrediPointsStorageLocation
bytes32 private constant CrediPointsStorageLocation =
0x37b77679eebf72087edeb9170a4792ec9f98e048226456d4588b7620b481c4fc;

function _getCrediPointsStorage() private pure returns (CrediPointsStorage storage $) {
assembly {
$.slot := CrediPointsStorageLocation
}
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(uint8 decimals_, address initialOwner, address underWritingAsset_) public initializer {
CrediPointsStorage storage $ = _getCrediPointsStorage();
require(underWritingAsset_ != address(0), CreditPoints_zeroAddress());
require(decimals_ > 0, CreditPoints_zeroAmount());
$.underWritingAsset = underWritingAsset_;
$.decimals = decimals_;

string memory name = string(abi.encodePacked("Credit Points - ", ERC20Upgradeable.name()));
string memory symbol = string(abi.encodePacked("cp-", ERC20Upgradeable.symbol()));

__ERC20_init(name, symbol);
__Ownable_init(initialOwner);
__ERC20Permit_init(name);
__UUPSUpgradeable_init();
_setApprovedReceiver(initialOwner, true);
}

function decimals() public view override returns (uint8) {
CrediPointsStorage storage $ = _getCrediPointsStorage();
return $.decimals;
}

function isApprovedReceiver(address receiver_) public view returns (bool) {
CrediPointsStorage storage $ = _getCrediPointsStorage();
return $.approvedReceivers[receiver_];
}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}

function burn(address from, uint256 amount) public onlyOwner {
_burn(from, amount);
}

function _update(address from, address to, uint256 value) internal override {
if (from != address(0)) {
require(isApprovedReceiver(to), "CrediPoints: transfer not approved");
}
super._update(from, to, value);
}

function _setApprovedReceiver(address receiver_, bool approved_) internal {
CrediPointsStorage storage $ = _getCrediPointsStorage();
$.approvedReceivers[receiver_] = approved_;
emit ApprovedTransactorSet(receiver_, approved_);
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
110 changes: 110 additions & 0 deletions src/CrediTalentCenter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {CrediPoints} from "./CrediPoints.sol";
import {FixedRateIrm} from "./FixedRateIrm.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {MarketParamsLib} from "@morpho/contracts/libraries/MarketParamsLib.sol";
import {IMorpho, Id, MarketParams, Market} from "@morpho/contracts/interfaces/IMorpho.sol";
import {IIrm} from "@morpho/contracts/interfaces/IIrm.sol";
import {IOracle} from "@morpho/contracts/interfaces/IOracle.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

enum ApplicationStatus {
None,
Pending,
Approved,
Rejected
}

struct Underwriter {
address underwriter;
uint256 approvalLimit;
}

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

contract CrediTalentCenter is IOracle, AccessControl {
using MarketParamsLib for MarketParams;

// cast keccak 'UNDERWRITER_ROLE'
bytes32 public constant UNDERWRITER_ROLE = 0xf63acc52fa4ad8a2695e14522f3df504db5c225cdd3d3a5acd3569b444572187;
uint256 public constant DEFAULT_LLTV = 0.98e18;

/// Events
event ApplicationCreated(uint256 id, address applicant, address receiver, bytes32 dataHash);
event UnderwriterSet(address indexed account, uint256 approvalLimit);
event FixedRateIrmSet(uint256 indexed interestRate, address irm);

/// Custom errors
error CrediTalentCenter_applicationAlreadyExists();
error CrediTalentCenter_fixedRateIrmAlreadyExists();

address public immutable underwritingAsset;
address public immutable creditPoints;
IMorpho public immutable morpho;
uint256 public applications;
mapping(uint256 => FixedRateIrm) public fixedRateIrms; // InterestRate (in WAD) => IIrm address
mapping(address => Underwriter) public underwriters;
mapping(address => Application) public applicationInfo;

constructor(
address underwritingAsset_,
CrediPoints crediPointsImpl_,
IMorpho morpho_,
uint256 defaultInterestRate
) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(UNDERWRITER_ROLE, msg.sender);
underwritingAsset = underwritingAsset_;
bytes memory initData =
abi.encodeWithSelector(CrediPoints.initialize.selector, address(this), underwritingAsset_);
creditPoints = address(new ERC1967Proxy(address(crediPointsImpl_), initData));
morpho = morpho_;
FixedRateIrm firm = _setFixedRateIrms(defaultInterestRate);
MarketParams memory marketParams =
MarketParams(underwritingAsset_, creditPoints, address(this), address(firm), DEFAULT_LLTV);
morpho.createMarket(marketParams);
}

/// @inheritdoc IOracle
function price() external view returns (uint256) {
uint256 underwriteAssetDecimals = IERC20Metadata(underwritingAsset).decimals();
uint256 scaleFactor = 10 ** (36 + underwriteAssetDecimals - IERC20Metadata(creditPoints).decimals());
return scaleFactor * 10 ** underwriteAssetDecimals;
}

function applyToCredit(bytes32 dataHash_, address receiver_) public {
require(applicationInfo[msg.sender].applicant == address(0), CrediTalentCenter_applicationAlreadyExists());
uint256 id = _useApplicationNumber();
applicationInfo[msg.sender] = Application(id, msg.sender, receiver_, dataHash_, ApplicationStatus.Pending);
emit ApplicationCreated(id, msg.sender, receiver_, dataHash_);
}

function setFixedRateIrms(uint256 newBorrowRate_) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setFixedRateIrms(newBorrowRate_);
}

function setUnderwriter(address underwriter_, uint256 approveLimit_) external onlyRole(DEFAULT_ADMIN_ROLE) {}

function _setFixedRateIrms(uint256 newBorrowRate_) internal returns (FixedRateIrm) {
require(address(fixedRateIrms[newBorrowRate_]) == address(0), CrediTalentCenter_fixedRateIrmAlreadyExists());
FixedRateIrm firm = new FixedRateIrm(newBorrowRate_);
fixedRateIrms[newBorrowRate_] = firm;
emit FixedRateIrmSet(newBorrowRate_, address(firm));
return firm;
}

function _useApplicationNumber() internal returns (uint256) {
applications += 1;
return applications;
}
}
Loading

0 comments on commit dfb38f7

Please sign in to comment.