Skip to content

Repository files navigation

Decentralized Lending Service

Final project for Peer2Peer Systems and Blockchains A.A. 2025/26.

This project implements a decentralized lending service on Ethereum. Contributors deposit ETH into a shared funding pool, applicants submit loan proposals, contributors vote with weight proportional to their disposable contribution, and every approved loan is managed by a dedicated LoanContract.

A trusted off-chain Bitcoin oracle scans Bitcoin data and publishes BTC-address liquidity to an on-chain BitcoinOracle, which the lending pool uses during proposal resolution.


1. Main architecture

Contributor deposits ETH
        ↓
Applicant requests/updates Bitcoin liquidity
        ↓
Applicant submits a loan proposal
        ↓
Contributors vote approve/reject
        ↓
Applicant requests proposal resolution
        ↓
LendingPool checks:
- available pool liquidity
- Bitcoin liquidity
- weighted vote result
        ↓
Approved proposal → dedicated LoanContract
        ↓
Borrower claims the principal
        ↓
Borrower repays through LendingPool
        ↓
Principal, interest, gains, and compensation are distributed

Main contracts

Contract Responsibility
LendingPool.sol Deposits, withdrawals, proposals, voting, fund locking, repayments, success/failure, compensation, and unclaimed-loan refunds
LoanContract.sol Dedicated state and ETH custody for one approved loan
BitcoinOracle.sol On-chain storage of Bitcoin-address balances and oracle-update requests
VulnerableLendingPool.sol Intentionally unsafe contract used only for the reentrancy demonstration
ReentrancyAttacker.sol Malicious contract used to attack the vulnerable pool

Important implemented rules

  • The borrower repays through one public path only: LendingPool.repayLoan().
  • A failed loan may still receive late repayments, but it remains permanently failed.
  • Principal repayment priority uses contributors' original locked amounts, not the changing remaining amounts.
  • Compensation can be claimed multiple times as the compensation pool is refilled.
  • loanLockedAmount represents the remaining uncompensated loss.
  • An expired loan that was never claimed is cancelled, refunded to the pool, and contributors' funds are unlocked.
  • The oracle minimum fee is calculated as:
updateBalance transaction gas used × 0.1 gwei
  • The genesis-prefunded account is used only for plain ETH transfers to newly generated accounts.
  • The production lending pool uses reentrancy protection.

2. Project structure

DecentralizedLendingProject/
├── contracts/
│   ├── BitcoinOracle.sol
│   ├── LendingPool.sol
│   ├── LoanContract.sol
│   ├── VulnerableLendingPool.sol
│   └── ReentrancyAttacker.sol
│
├── scripts/
│   ├── setup_service.py
│   ├── example_operations.py
│   ├── auto_contributor.py
│   ├── gas_evaluation.py
│   ├── compensation_gas.py
│   ├── bitcoin_oracle.py
│   ├── bitcoin_oracle_full.py
│   └── deployed_addresses.json       # Generated by setup_service.py
│
├── test/
│   ├── BitcoinOracle.test.js
│   ├── FailedLoanRepayment.test.js
│   ├── GasEvaluation.test.js
│   ├── LoanContract.test.js
│   ├── LoanProposal.test.js
│   ├── OracleMinimumFee.test.js
│   ├── OrderedRepayment.test.js
│   ├── OriginalRepaymentOrder.test.js
│   ├── ProposalResolution.test.js
│   ├── ReentrancyAttack.test.js
│   ├── ReentrancyVulnerable.test.js
│   └── UnclaimedLoanRefund.test.js
│
├── report/
│   └── Decentralized_Lending_Service_Report.pdf
│
├── hardhat.config.js
├── package.json
├── package-lock.json
├── project2526genesis.json
├── block100000.json
├── oracle_131000_output.txt
├── .env.example
├── .gitignore
└── README.md

3. Requirements

Install:

  • Node.js and npm
  • Python 3.10 or newer
  • Bitcoin Core and bitcoin-cli only for the full Bitcoin scan
  • Geth only when using the supplied course private chain

Install Python packages:

python3 -m pip install web3 python-dotenv

4. Install and compile

From the project folder:

cd DecentralizedLendingProject
npm install

Compile the smart contracts:

npx hardhat compile

Equivalent npm command:

npm run compile

Compilation creates the ABI and bytecode files inside artifacts/. The Python deployment scripts require these files.


5. Environment configuration

Create the real environment file:

cp .env.example .env

5.1 Course private-chain configuration

The account funded in project2526genesis.json must be available through one of the following credential methods.

Option A: raw private key

RPC_URL=http://127.0.0.1:8545
EXPECTED_CHAIN_ID=202526
EXPECTED_PREFUNDED_ADDRESS=0xd278d247A52C550508ea2b2C9321d816238fb523

PREFUNDED_PRIVATE_KEY=0x64_HEXADECIMAL_CHARACTERS
PREFUNDED_KEYSTORE_FILE=
PREFUNDED_KEYSTORE_PASSWORD=
PREFUNDED_KEYSTORE_PASSWORD_FILE=

DEPLOYER_FUND_ETH=100
CONTRIBUTOR_FUND_ETH=20
BITCOIN_CONF="/absolute/path/to/bitcoin.conf"

Option B: supplied course Geth keystore JSON

The project package contains the matching encrypted course keystore and its password file. The default relative configuration is:

RPC_URL=http://127.0.0.1:8545
EXPECTED_CHAIN_ID=202526
EXPECTED_PREFUNDED_ADDRESS=0xd278d247A52C550508ea2b2C9321d816238fb523

PREFUNDED_PRIVATE_KEY=
PREFUNDED_KEYSTORE_FILE=private-chain-data/keystore/UTC--2026-05-05T14-09-10.723312492Z--d278d247a52c550508ea2b2c9321d816238fb523
PREFUNDED_KEYSTORE_PASSWORD=
PREFUNDED_KEYSTORE_PASSWORD_FILE=0xd278d247A52C550508ea2b2C9321d816238fb523psw.txt

DEPLOYER_FUND_ETH=100
CONTRIBUTOR_FUND_ETH=20
BITCOIN_CONF="/absolute/path/to/bitcoin.conf"

To apply this configuration automatically to an existing .env, run:

python3 scripts/configure_course_credentials.py

The script decrypts the keystore only to verify its address. It does not print or save the decrypted private key.

Alternatively, put the password directly in PREFUNDED_KEYSTORE_PASSWORD and leave PREFUNDED_KEYSTORE_PASSWORD_FILE empty. Do not configure both.

The supplied password text project2526 is used only to decrypt the included Geth keystore. Do not put that password in PREFUNDED_PRIVATE_KEY.

setup_service.py now validates:

  • the raw key format;
  • the keystore file and password;
  • the derived account address against the genesis address;
  • the chain ID and prefunded account balance.

It prints a specific configuration error instead of treating a placeholder or password as a private key.

5.2 Hardhat development configuration

For a fast local demonstration, start npx hardhat node, then use Account #0 as the prefunded development account:

RPC_URL=http://127.0.0.1:8545
EXPECTED_CHAIN_ID=31337
EXPECTED_PREFUNDED_ADDRESS=hardhat_account_0_address
PREFUNDED_PRIVATE_KEY=hardhat_account_0_private_key
PREFUNDED_KEYSTORE_FILE=
PREFUNDED_KEYSTORE_PASSWORD=
PREFUNDED_KEYSTORE_PASSWORD_FILE=

DEPLOYER_FUND_ETH=100
CONTRIBUTOR_FUND_ETH=20

BITCOIN_CONF="/absolute/path/to/bitcoin.conf"

Hardhat's printed keys are public development keys. Never send real ETH to them.

Automatic generated-account configuration

setup_service.py creates:

  • one deployer/oracle-owner account;
  • two contributor accounts.

The setup script automatically writes their keys into .env as:

PRIVATE_KEY=generated_deployer_private_key
PRIVATE_KEY_1=generated_contributor_1_private_key
PRIVATE_KEY_2=generated_contributor_2_private_key

It also saves a backup in .env.generated.

No manual copying is required.

Never commit or submit .env or .env.generated because they contain private keys.


6. Run the test suite

The test command creates its own temporary Hardhat blockchain. A separate npx hardhat node process is not required.

Run all tests:

npm test

Equivalent command:

npx hardhat test

Expected result for the current project:

41 passing

Run individual test files:

npx hardhat test test/BitcoinOracle.test.js
npx hardhat test test/FailedLoanRepayment.test.js
npx hardhat test test/GasEvaluation.test.js
npx hardhat test test/LoanContract.test.js
npx hardhat test test/LoanProposal.test.js
npx hardhat test test/OracleMinimumFee.test.js
npx hardhat test test/OrderedRepayment.test.js
npx hardhat test test/OriginalRepaymentOrder.test.js
npx hardhat test test/ProposalResolution.test.js
npx hardhat test test/ReentrancyAttack.test.js
npx hardhat test test/ReentrancyVulnerable.test.js
npx hardhat test test/UnclaimedLoanRefund.test.js

7. Fast local demonstration with Hardhat

Use two terminals.

Terminal 1: start the local blockchain

cd DecentralizedLendingProject
npx hardhat node

Keep this terminal open.

Terminal 2: compile and configure .env

cd DecentralizedLendingProject
npx hardhat compile
cp .env.example .env

Edit .env and configure the Hardhat Account #0 address and private key as described in section 5.2.

Deploy the service

python3 scripts/setup_service.py

The script:

  1. verifies the RPC connection and chain ID;
  2. verifies the prefunded account;
  3. generates a deployer and two contributor accounts;
  4. uses the prefunded account only to fund those accounts;
  5. deploys a temporary oracle;
  6. measures real updateBalance() gas usage;
  7. calculates the production oracle minimum fee;
  8. deploys BitcoinOracle and LendingPool from the generated deployer;
  9. writes generated account keys automatically into .env;
  10. saves deployment metadata in scripts/deployed_addresses.json.

Run the full example

python3 scripts/example_operations.py

The example demonstrates:

  1. contributor deposit;
  2. Bitcoin-oracle balance update;
  3. loan-proposal submission;
  4. contributor voting;
  5. voting-period block progression;
  6. proposal resolution;
  7. dedicated LoanContract creation;
  8. borrower loan claim;
  9. borrower repayment through LendingPool;
  10. principal/interest distribution and final state output.

8. Script reference

Script When to run Purpose
setup_service.py First, after starting a fresh blockchain Creates/funds accounts, measures oracle gas, calculates the fee, deploys contracts, and saves configuration
example_operations.py After setup Runs the complete normal lending workflow
auto_contributor.py After setup, in a separate terminal Watches for proposals and automatically votes approve
bitcoin_oracle.py After setup Demonstrates request, owner update, and reading of one BTC balance
bitcoin_oracle_full.py After Bitcoin Core is ready Processes Bitcoin blocks, builds a UTXO set, calculates a balance, and optionally updates Ethereum
gas_evaluation.py After setup Measures gas usage for the main operations
compensation_gas.py After setup Measures failure/compensation-related gas usage

deployed_addresses.json is data, not an executable script. It is regenerated by setup_service.py.


9. Automated contributor

After deployment, run in a separate terminal:

python3 scripts/auto_contributor.py

The script:

  • loads the generated accounts from .env;
  • ensures they have disposable funds;
  • watches for new proposals;
  • votes approve when necessary.

Stop it with:

Ctrl + C

A final KeyboardInterrupt message after pressing Ctrl + C is normal.


10. Gas evaluation

Run the Python gas scripts after setup:

python3 scripts/gas_evaluation.py
python3 scripts/compensation_gas.py

Run the Hardhat gas test:

npx hardhat test test/GasEvaluation.test.js

The oracle-fe test verifies:

npx hardhat test test/OracleMinimumFee.test.js

11. Reentrancy demonstration

The production contract is protected by OpenZeppelin ReentrancyGuard and the checks-effects-interactions pattern.

The vulnerable demonstration uses:

  • VulnerableLendingPool.sol;
  • ReentrancyAttacker.sol.

Run:

npx hardhat test test/ReentrancyVulnerable.test.js
npx hardhat test test/ReentrancyAttack.test.js

The first test proves that the intentionally vulnerable contract can be drained. The second proves that the production pool blocks the same attack.


12. Simple Bitcoin oracle demonstration

After deployment:

python3 scripts/bitcoin_oracle.py

The script:

  1. pays the minimum fee to request a balance update;
  2. uses the oracle-owner account to store a sample BTC balance;
  3. reads the stored balance from Ethereum.

13. Full Bitcoin oracle scan

This requires Bitcoin Core, bitcoin-cli, and access to the required historical blocks.

Configure an absolute BITCOIN_CONF path in .env, for example:

BITCOIN_CONF="/Users/username/Library/Application Support/Bitcoin/bitcoin.conf"

Run the project scan:

python3 -u scripts/bitcoin_oracle_full.py \
  --to-block 131000 \
  --address 1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn \
  --verbose-every 1000 \
  --update-ethereum | tee oracle_131000_output.txt

Run without updating Ethereum:

python3 -u scripts/bitcoin_oracle_full.py \
  --to-block 131000 \
  --address 1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn \
  --verbose-every 1000

Arguments:

Argument Meaning
--to-block Last Bitcoin block height to process
--address Bitcoin address whose UTXO balance is calculated
--verbose-every Progress-print interval
--update-ethereum Also writes the calculated balance to BitcoinOracle

14. Supplied private chain

The official project includes project2526genesis.json, whose chain ID is 202526 and whose prefunded signer address is:

0xd278d247A52C550508ea2b2C9321d816238fb523

Initialize a Geth data directory:

mkdir -p ethdata
geth --datadir ethdata init project2526genesis.json

The matching course signer keystore is included in private-chain-data/keystore/. Configure .env by running:

python3 scripts/configure_course_credentials.py

Then start the Clique node using the course-provided Geth version and commands. The node must expose HTTP RPC at http://127.0.0.1:8545 and produce new blocks. Modern Geth releases no longer seal Clique blocks, so use the version specified by the course environment rather than automatically upgrading Geth.

Run:

npx hardhat compile
python3 scripts/setup_service.py

The prefunded genesis account performs only ETH transfers. All contract deployment is performed by the newly generated deployer account.

Development-chain note

example_operations.py uses Hardhat's evm_mine RPC method to advance blocks quickly. For the course Geth chain, allow Clique to produce blocks normally or replace the development mining helper with a wait-for-blocks loop.


15. Stop, reset, and restart

Stop a running Hardhat node with:

Ctrl + C

A new npx hardhat node starts a fresh empty blockchain, so the contracts must be deployed again.

Fresh Hardhat restart

Terminal 1:

npx hardhat node

Terminal 2:

npx hardhat compile
python3 scripts/setup_service.py
python3 scripts/example_operations.py

The setup script automatically replaces the generated PRIVATE_KEY, PRIVATE_KEY_1, and PRIVATE_KEY_2 values in .env.

Clean build files

npx hardhat clean
npx hardhat compile

Remove generated deployment data

rm -f scripts/deployed_addresses.json .env.generated

16. Generated and private files

Do not commit or submit:

.env
.env.generated
.env.save
scripts/deployed_addresses.json
node_modules/
artifacts/
cache/
.git/
.DS_Store
__MACOSX/

Keep and submit:

.env.example
.gitignore
contracts/
scripts/
test/
report/
package.json
package-lock.json
hardhat.config.js
project2526genesis.json
README.md

Commands:

npx hardhat test
npx hardhat test test/FailedLoanRepayment.test.js
npx hardhat test test/OriginalRepaymentOrder.test.js
npx hardhat test test/UnclaimedLoanRefund.test.js
npx hardhat test test/ReentrancyVulnerable.test.js
npx hardhat test test/ReentrancyAttack.test.js

17. Quick command reference

# Install dependencies
npm install
python3 -m pip install web3 python-dotenv

# Compile
npx hardhat compile

# Test
npx hardhat test

# Start local development blockchain
npx hardhat node

# Deploy/configure service
python3 scripts/setup_service.py

# Run complete demo
python3 scripts/example_operations.py

# Run automated contributor
python3 scripts/auto_contributor.py

# Run simple oracle demo
python3 scripts/bitcoin_oracle.py

# Gas evaluation
python3 scripts/gas_evaluation.py
python3 scripts/compensation_gas.py

About

Ethereum-based decentralized lending service with weighted contributor voting, dedicated loan contracts, compensation mechanisms, and a Bitcoin liquidity oracle.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages