Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ba3c33f
forge install: openzeppelin-contracts
pmatsinopoulos Mar 1, 2025
7e4c986
When we deploy the contract it set the owner to be the entity that de…
pmatsinopoulos Mar 1, 2025
735a686
When deploying we set the ERC20 collateralToken
pmatsinopoulos Mar 1, 2025
0ff5176
Removed sample contract 'Counter'
pmatsinopoulos Mar 1, 2025
b32784e
Added 'remappings.txt'. Makes VS Code work better
pmatsinopoulos Mar 1, 2025
60d097b
Use a 'setUp()' function to initialize state of the Test contract bef…
pmatsinopoulos Mar 1, 2025
1b7a76d
Setting interest rate
pmatsinopoulos Mar 1, 2025
338c5c0
First test for requestLoan
pmatsinopoulos Mar 2, 2025
b883700
Use a function to make borrower have an active loan
pmatsinopoulos Mar 2, 2025
7a8c459
Forge fmt
pmatsinopoulos Mar 2, 2025
e5886f5
Tune the github workflow actions
pmatsinopoulos Mar 2, 2025
70132ec
Test for NotEnoughCollateralProvidedForBorrowedAmountError
pmatsinopoulos Mar 2, 2025
884a11c
Test for LenderDoesNotHaveEnoughEtherError
pmatsinopoulos Mar 2, 2025
e929af3
Test for BorrowerDoesNotHaveEnoughCollateralError
pmatsinopoulos Mar 2, 2025
151e9b2
Finished testing the requestLoan
pmatsinopoulos Mar 2, 2025
11f3fe3
Tests for #myLoanInfo()
pmatsinopoulos Mar 2, 2025
149a32b
[doc] - About the asdf version
pmatsinopoulos Mar 2, 2025
09914d3
Some bash scripts that use 'forge create' to deploy locally
pmatsinopoulos Mar 2, 2025
1531c89
Deploy to local anvil using 'forge script'
pmatsinopoulos Mar 2, 2025
46b7892
Examples of deploying and running the contract locally
pmatsinopoulos Mar 3, 2025
ead4fc4
Pin Python 3.12.3
pmatsinopoulos Mar 9, 2025
19a12ee
Merge remote-tracking branch 'origin/main' into collaterized-loan-sma…
pmatsinopoulos Mar 9, 2025
ab4f313
Added CollateralCalculator
pmatsinopoulos Mar 9, 2025
bfb2308
Trying to test the verifyProof
pmatsinopoulos Mar 9, 2025
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
7 changes: 6 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ name: CI

on:
push:
branches:
- main
pull_request:
branches:
- main
types: [ready_for_review, opened, reopened, synchronize]
workflow_dispatch:

env:
Expand Down Expand Up @@ -39,5 +44,5 @@ jobs:

- name: Run Forge tests
run: |
forge test -vvv
forge test -vvvvv
id: test
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python 3.12.3
solidity 0.8.28
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

## `asdf` - 3rd Party Tools Version Manager

**Important:** I have encountered installation problems with `v0.16.x`. So, I have
stayed to `v0.15.x`.

We are suggesting [asdf](https://asdf-vm.com/) as the version manager for 3rd party tools.
This is an awesome tool that runs on many different platforms and uses one single file to
manage all tools versions. The file is `.tool-versions`.
Expand Down Expand Up @@ -45,3 +48,70 @@ Then I restarted my shell and used the command:
```
foundryup --install stable
```

## Deployment of Smart Contract(s)

Start a local chain:

```bash
$ anvil
...
Listening 127.0.0.1:8545

```

### Using `forge create` (not preferred)

Then you can run the following commands to deploy to the local network:

Note: For owner I use the first owner that `anvil` provides me with. Same for private key.

#### Step 1

Deploy the ERCMock contract. Note down its address:

```bash
$ ./fc_deploy_ercmock.sh 'http://127.0.0.1:8545' '0xac..a...private key....80'
```

#### Step 2

Deploy the Collateralized Loan contract:

```bash
$ ./fc_deploy_collateralized_loan.sh 'http://127.0.0.1:8545' '0xac..a...private key....80' '0xf39Fd...an owner...ffFb92266' '0x5Fb...ERC20Mock Address...0aa3' 20 5
```

### Using `forge script`

```bash
$ ./fs_deploy_collateralized_loan.sh 'http://127.0.0.1:8545' <keystore account>
```

### Request Some Loan

After having done the deployment, the following can be used to execute a request for loan:

```bash
$ ./fs_request_loan.sh 'http://127.0.0.1:8545' <sender> <keystore1> <keystore2>
```

This will send 30 Ether to `0x70997970C51812dc3A010C7d01b50e0d17dc79C8`. Hence, this account will then have
balance: `10029.999872713728857791`, which you can check with:

```bash
cast balance --rpc-url 'http://127.0.0.1:8545' 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --ether
```

And the contract will have 30 ether less: `70.000000000000000000`. You can check it with:

```bash
$ cast balance --rpc-url 'http://127.0.0.1:8545' 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --ether
```

And you can also run the following script to see what is the smart contract in `ERC20Mock`
tokens.

```bash
$ cast balance --rpc-url 'http://127.0.0.1:8545' --ether --erc20 0x5FbDB2315678afecb367f032d93F642f64180aa3 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
```
15 changes: 15 additions & 0 deletions fc_deploy_collateralized_loan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

set -euox pipefail
shopt -s globstar

RPC_URL=$1
PRIVATE_KEY=$2
INITIAL_OWNER=$3
ERC20_TOKEN=$4
INTEREST_RATE=$5
MIN_COLLATERALIZATION_RATIO=$6

forge create --rpc-url "${RPC_URL}" --private-key "${PRIVATE_KEY}" src/CollateralizedLoan.sol:CollateralizedLoan \
--broadcast \
--constructor-args "${INITIAL_OWNER}" "${ERC20_TOKEN}" ${INTEREST_RATE} ${MIN_COLLATERALIZATION_RATIO} \
10 changes: 10 additions & 0 deletions fc_deploy_ercmock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash

set -euox pipefail
shopt -s globstar

RPC_URL=$1
PRIVATE_KEY=$2

forge create --rpc-url "${RPC_URL}" --private-key "${PRIVATE_KEY}" lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol:ERC20Mock \
--broadcast
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 2000
via_ir = true
solc_version = "0.8.28"

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
10 changes: 10 additions & 0 deletions fs_deploy_collateralized_loan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash

set -euox pipefail
shopt -s globstar

RPC_URL=$1
KEYSTORE=$2

forge script script/CollateralizedLoan.s.sol --broadcast \
--rpc-url "${RPC_URL}" --keystore "${HOME}/.foundry/keystores/${KEYSTORE}"
14 changes: 14 additions & 0 deletions fs_request_loan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

set -euox pipefail
shopt -s globstar

RPC_URL=$1
SENDER=$2
KEYSTORE1=$3
KEYSTORE2=$4

forge script script/ExampleRequestLoan.s.sol \
--rpc-url "${RPC_URL}" --keystore $HOME/.foundry/keystores/${KEYSTORE1} \
--keystore $HOME/.foundry/keystores/${KEYSTORE2} \
--broadcast --sender ${SENDER}
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at acd4ff
5 changes: 5 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
35 changes: 35 additions & 0 deletions script/CollateralizedLoan.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
import {Script, console} from "forge-std/Script.sol";
import {CollateralizedLoan} from "../src/CollateralizedLoan.sol";

contract CollateralizedLoanScript is Script {
uint256 constant LOCAL_ANVIL_CHAIN_ID = 31337;

function run() public {
if (localAnvilChainId()) {
vm.startBroadcast();

address initialOwner = msg.sender;
ERC20Mock collateralToken = new ERC20Mock();
uint256 interestRate = 20;
uint256 minCollateralizationRatio = 5;

new CollateralizedLoan{value: 100 ether}(
initialOwner, collateralToken, interestRate, minCollateralizationRatio
);

vm.stopBroadcast();
}
}

// ------------------
// PRIVATE
// ------------------

function localAnvilChainId() private view returns (bool) {
return (block.chainid == LOCAL_ANVIL_CHAIN_ID);
}
}
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

31 changes: 31 additions & 0 deletions script/ExampleRequestLoan.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// // SPDX-License-Identifier: UNLICENSED
// pragma solidity ^0.8.28;

// import {Vm} from "lib/forge-std/src/Vm.sol";

// import {CollateralizedLoan} from "src/CollateralizedLoan.sol";
// import {console} from "forge-std/Test.sol";
// import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";

// contract ExampleRequestLoan {
// Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

// /// @notice REPL contract entry point
// function run() public {
// CollateralizedLoan cl = CollateralizedLoan(0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512);
// address owner = cl.owner();
// console.log("owner", owner);
// ERC20Mock collateralToken = ERC20Mock(0x5FbDB2315678afecb367f032d93F642f64180aa3);
// address borrower = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;
// uint256 minimumCollateral = cl.minimumCollateralRequired(30 ether);

// vm.startBroadcast();
// collateralToken.mint(borrower, minimumCollateral);
// vm.stopBroadcast();

// vm.startBroadcast(borrower);
// collateralToken.approve(address(cl), minimumCollateral);
// cl.requestLoan(30 ether, minimumCollateral);
// vm.stopBroadcast();
// }
// }
93 changes: 93 additions & 0 deletions src/CollateralCalculator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {ICollateralCalculator} from "./ICollateralCalculator.sol";

/**
* @title CollateralCalculator
* @dev Calculates required loan collateral based on credit score
* This contract is used to calculate the required collateral for a loan based on the borrower's credit score.
* The credit score is verified on-chain with another smart contract (to be provided) and passed to this contract to calculate the required collateral.
* The required collateral is calculated as a percentage of the loan amount based on the borrower's credit tier.
*
*/
contract CollateralCalculator is ICollateralCalculator {
mapping(address borrower => CreditTier creditTier) private s_borrowerCreditTiers;

uint8[4] private s_creditTierCollateralPercentages = [
120, // 120 %
100, // 100 %
90, // 90 %
80 // 80 %
];

// ------------------
// external
// ------------------

/*
* This should only be called by the Smart Contract that delivers the Loan, i.e. by the +CollateralizedLoan+ Smart Contract.
*/
function updateCreditScore(address _borrower, uint256 _creditScore, bool _proofValidated) external {
CreditTier newTier;

if (!_proofValidated) {
newTier = CreditTier.UNKNOWN;
} else {
if (_creditScore < 400) {
// _creditScore < 400
newTier = CreditTier.LOW;
} else if (_creditScore < 700) {
// 400 <= _creditScore < 700
newTier = CreditTier.MEDIUM;
} else {
// > 0.7
newTier = CreditTier.HIGH;
}
}

s_borrowerCreditTiers[_borrower] = newTier;

emit CreditScoreUpdated(_borrower, _creditScore, newTier);
}

// ------------------
// external view
// ------------------

function getBorrowerCreditTier(address _borrower) external view override returns (CreditTier) {
return s_borrowerCreditTiers[_borrower];
}

/**
* TODO: We will see how: This may be called only by the +CollateralizedLoan+ Smart Contract.
*/
function getCollateralRequirement(address _borrower, uint256 _loanAmount)
external
view
override
returns (CollateralRequirement memory)
{
// Get borrower credit tier
CreditTier tier = s_borrowerCreditTiers[_borrower];

// Get required collateral percentage for that tier
uint8 requiredPercentage = s_creditTierCollateralPercentages[uint256(tier)];

// Calculate required collateral amount
uint256 requiredAmount = calculateRequiredAmount(_loanAmount, requiredPercentage);

return CollateralRequirement(requiredPercentage, requiredAmount, tier);
}

// ------------------
// internal
// ------------------
function calculateRequiredAmount(uint256 _loanAmount, uint256 _requiredPercentage)
internal
pure
returns (uint256)
{
return (_loanAmount * _requiredPercentage) / 100;
}
}
Loading