Skip to main content

InsurancePool Contract

The InsurancePool contract collects fees from loans and covers lender losses when borrowers default.

Contract Overview

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract InsurancePool is ReentrancyGuard, AccessControl {
    IERC20 public immutable usdc;

    uint256 public totalPoolBalance;
    uint256 public totalClaimsPaid;
    uint256 public totalFeesCollected;

    // Coverage ratio: 80% of defaults covered
    uint256 public constant COVERAGE_RATIO = 80;

    // Minimum pool balance before restricting new loans
    uint256 public minimumPoolBalance;

    struct Claim {
        uint256 loanId;
        uint256 amount;
        uint256 timestamp;
        bool paid;
    }

    mapping(uint256 => Claim) public claims;
    uint256 public claimCounter;

    // Surplus distribution tracking
    mapping(address => uint256) public contributorShares;
    uint256 public totalShares;
}

Key Functions

Deposit Insurance Fee

Called when a new loan is created:
function deposit(uint256 amount) external onlyRole(LOAN_CONTRACT_ROLE) {
    usdc.transferFrom(msg.sender, address(this), amount);
    totalPoolBalance += amount;
    totalFeesCollected += amount;

    emit FeeDeposited(amount, totalPoolBalance);
}

Claim for Default

Called when a loan defaults:
function claimForDefault(
    uint256 loanId,
    uint256 requestedAmount
) external onlyRole(LOAN_CONTRACT_ROLE) returns (uint256 paidAmount) {
    require(claims[loanId].loanId == 0, "Already claimed");

    // Calculate coverage (80% of loss)
    uint256 maxCoverage = (requestedAmount * COVERAGE_RATIO) / 100;

    // Pay what we can, up to max coverage
    paidAmount = maxCoverage > totalPoolBalance ? totalPoolBalance : maxCoverage;

    claims[++claimCounter] = Claim({
        loanId: loanId,
        amount: paidAmount,
        timestamp: block.timestamp,
        paid: true
    });

    totalPoolBalance -= paidAmount;
    totalClaimsPaid += paidAmount;

    // Transfer to Trust Vault to cover staker losses
    usdc.transfer(msg.sender, paidAmount);

    emit ClaimPaid(loanId, paidAmount, totalPoolBalance);

    return paidAmount;
}

Voluntary Contribution

Users can contribute to increase coverage and earn surplus distributions:
function contribute(uint256 amount) external nonReentrant {
    require(amount >= MIN_CONTRIBUTION, "Below minimum");

    usdc.transferFrom(msg.sender, address(this), amount);

    // Calculate shares based on contribution
    uint256 shares = (totalShares == 0)
        ? amount
        : (amount * totalShares) / totalPoolBalance;

    contributorShares[msg.sender] += shares;
    totalShares += shares;
    totalPoolBalance += amount;

    emit ContributionMade(msg.sender, amount, shares);
}

Withdraw Contribution

Contributors can withdraw their share plus earned surplus:
function withdrawContribution(uint256 shares) external nonReentrant {
    require(contributorShares[msg.sender] >= shares, "Insufficient shares");

    uint256 withdrawAmount = (shares * totalPoolBalance) / totalShares;

    // Ensure minimum pool balance maintained
    require(
        totalPoolBalance - withdrawAmount >= minimumPoolBalance,
        "Would breach minimum"
    );

    contributorShares[msg.sender] -= shares;
    totalShares -= shares;
    totalPoolBalance -= withdrawAmount;

    usdc.transfer(msg.sender, withdrawAmount);

    emit ContributionWithdrawn(msg.sender, withdrawAmount, shares);
}

Distribute Surplus

When pool grows beyond target, distribute surplus to contributors:
function distributeSurplus() external onlyRole(POOL_MANAGER_ROLE) {
    uint256 targetBalance = calculateTargetBalance();
    require(totalPoolBalance > targetBalance, "No surplus");

    uint256 surplus = totalPoolBalance - targetBalance;

    // Keep 20% as buffer
    uint256 distributableSurplus = (surplus * 80) / 100;

    // Distribute pro-rata to contributors
    // (Actual distribution happens via claim mechanism)

    emit SurplusDistributed(distributableSurplus, totalShares);
}

View Functions

Get Pool Status

function getPoolStatus() external view returns (
    uint256 balance,
    uint256 claimsPaid,
    uint256 feesCollected,
    uint256 coverageCapacity,
    uint256 utilizationRatio
) {
    uint256 capacity = (totalPoolBalance * 100) / COVERAGE_RATIO;
    uint256 utilization = totalClaimsPaid > 0
        ? (totalClaimsPaid * 100) / totalFeesCollected
        : 0;

    return (
        totalPoolBalance,
        totalClaimsPaid,
        totalFeesCollected,
        capacity,
        utilization
    );
}

Get Contributor Info

function getContributorInfo(address contributor) external view returns (
    uint256 shares,
    uint256 shareValue,
    uint256 percentageOfPool
) {
    uint256 userShares = contributorShares[contributor];
    uint256 value = totalShares > 0
        ? (userShares * totalPoolBalance) / totalShares
        : 0;
    uint256 percentage = totalShares > 0
        ? (userShares * 10000) / totalShares
        : 0;

    return (userShares, value, percentage);
}

Events

event FeeDeposited(uint256 amount, uint256 newBalance);
event ClaimPaid(uint256 indexed loanId, uint256 amount, uint256 remainingBalance);
event ContributionMade(address indexed contributor, uint256 amount, uint256 shares);
event ContributionWithdrawn(address indexed contributor, uint256 amount, uint256 shares);
event SurplusDistributed(uint256 amount, uint256 totalShares);
event MinimumBalanceUpdated(uint256 oldMinimum, uint256 newMinimum);

Pool Economics

Fee Collection

┌─────────────────────────────────────────────────────────────────┐
│  Every Loan: 10% Insurance Fee                                  │
├─────────────────────────────────────────────────────────────────┤
│  $100 loan requested                                            │
│  └── $10 → Insurance Pool                                       │
│  └── $90 → Borrower receives                                    │
└─────────────────────────────────────────────────────────────────┘

Default Coverage

┌─────────────────────────────────────────────────────────────────┐
│  When Loan Defaults: 80% Coverage                               │
├─────────────────────────────────────────────────────────────────┤
│  $100 loan defaults                                             │
│  └── $80 → Covered by Insurance Pool                            │
│  └── $20 → Absorbed by Trust Vault stakers                      │
└─────────────────────────────────────────────────────────────────┘

Surplus Distribution

Pool StatusAction
Below minimumNew loans restricted
At targetNormal operation
Above targetSurplus distributed to contributors

Constants

ConstantValueDescription
COVERAGE_RATIO80Percentage of defaults covered
INSURANCE_FEE_BPS100010% fee on loans
MIN_CONTRIBUTION100 USDCMinimum voluntary contribution
SURPLUS_THRESHOLD120%When to distribute surplus

Access Control

RolePermissions
LOAN_CONTRACT_ROLEDeposit fees, claim for defaults
POOL_MANAGER_ROLEDistribute surplus, update parameters
DEFAULT_ADMIN_ROLEManage roles

Integration Example

import { ethers } from 'ethers';

// Check pool status
const status = await insurancePool.getPoolStatus();
console.log(`Pool Balance: $${ethers.formatUnits(status.balance, 6)}`);
console.log(`Coverage Capacity: $${ethers.formatUnits(status.coverageCapacity, 6)}`);

// Contribute to pool
await usdc.approve(insurancePool.address, contribution);
await insurancePool.contribute(ethers.parseUnits("1000", 6));

// Check your contribution
const info = await insurancePool.getContributorInfo(myAddress);
console.log(`Your share value: $${ethers.formatUnits(info.shareValue, 6)}`);