Protocol Architecture

Osito Protocol consists of a set of immutable, non-upgradable smart contracts that work together to implement a mathematical lending model. This document explains how the protocol's architecture calculates safe lending limits based on AMM pool state.

Core Design Philosophy

The architecture is built around a mathematical approach to lending limits:

Instead of relying on price oracles, the protocol calculates safe lending limits based on AMM pool state.

This approach allows the protocol to determine lending limits using only on-chain data, without needing external price feeds. The core formula is:

max_borrow = pool_BERA - extractable_BERA

Where extractable_BERA represents the maximum BERA that could be extracted by selling all circulating tokens into the pool.

Contract Overview

The protocol consists of three core contracts:

  1. OsitoToken: Verifies token eligibility and provides pool data for borrow limit calculations
  2. OsitoLending: Implements the core lending, borrowing, and staking functionality
  3. OsitoFactory: Deploys and links the other contracts (one-time deployment)

Supporting libraries:

  • OsitoBorrowLimit: Implements the mathematical model for calculating borrow limits
  • OsitoRateCurve: Implements the standard two-slope interest rate model

Contract Relationships

┌───────────────────┐
│   OsitoFactory    │
│  (Deployer Only)  │
└─────────┬─────────┘
          │ deploys
          ▼
┌───────────────────┐       references       ┌───────────────────┐
│    OsitoToken     │◄─────────────────────►│   OsitoLending    │
│ (Eligibility/Data) │                       │  (Core Protocol)   │
└───────────────────┘                       └────────┬──────────┘
                                                     │
                                                     ▼
                                           ┌───────────────────┐
                                           │  AMM Pool Data    │
                                           │  (Borrow Calc)     │
                                           └───────────────────┘

OsitoToken Contract: Token Verification

The OsitoToken contract serves two key functions:

  1. Verify token eligibility to ensure the mathematical model can be applied
  2. Provide pool data for borrow limit calculations

Eligibility Verification

The contract verifies two objective criteria:

function isEligibleToken(address token) external view override returns (bool) {
    // 1. Check fixed supply (deployed by whitelisted factory)
    if (!isWhitelistedDeployer(token)) {
        return false;
    }
    
    // 2. Check valid liquidity pool
    address pair = getTokenWberaPair(token);
    if (pair == address(0)) {
        return false;
    }
    
    // 3. Check burned LP (prevents direct liquidity withdrawal)
    uint256 burnedLP = IERC20(pair).balanceOf(BURN_ADDRESS);
    if (burnedLP < MINIMUM_BURNED_LP) {
        return false;
    }
    
    return true;
}

Pool Data For Borrow Calculations

The contract provides data needed for the borrow limit formula:

function getTokensInPool(address token) external view override returns (uint256) {
    address pair = getTokenWberaPair(token);
    if (pair == address(0)) return 0;
    
    // Get reserves from the pair
    (uint112 reserve0, uint112 reserve1, ) = IKodiakPair(pair).getReserves();
    
    // Determine which reserve is the token
    address token0 = IKodiakPair(pair).token0();
    return token0 == token ? uint256(reserve0) : uint256(reserve1);
}

function getWberaInPool(address token) external view override returns (uint256) {
    address pair = getTokenWberaPair(token);
    if (pair == address(0)) return 0;
    
    // Get reserves from the pair
    (uint112 reserve0, uint112 reserve1, ) = IKodiakPair(pair).getReserves();
    
    // Determine which reserve is wBERA
    address token0 = IKodiakPair(pair).token0();
    return token0 == wbera ? uint256(reserve0) : uint256(reserve1);
}

function getTokenTotalSupply(address token) external view override returns (uint256) {
    return IERC20(token).totalSupply();
}

OsitoBorrowLimit Library: Mathematical Model

This library implements the core borrow limit calculation:

function calculateMaxBorrow(
    address token,
    IOsitoToken tokenVerifier,
    uint256 tokensDeposited,
    uint256 tokensStaked
) internal view returns (uint256) {
    // Get token information from the verifier
    uint256 totalSupply = tokenVerifier.getTokenTotalSupply(token);
    uint256 tokensInPool = tokenVerifier.getTokensInPool(token);
    uint256 wberaInPool = tokenVerifier.getWberaInPool(token);
    
    // Early safety checks
    if (wberaInPool == 0 || tokensInPool == 0) {
        return 0;
    }
    
    // Calculate tokens controlled by protocol (deposited + staked)
    uint256 protocolControlledTokens = tokensDeposited + tokensStaked;
    
    // Calculate dumpable tokens
    uint256 dumpableTokens;
    if (totalSupply > (tokensInPool + protocolControlledTokens)) {
        unchecked {
            dumpableTokens = totalSupply - (tokensInPool + protocolControlledTokens);
        }
    } else {
        dumpableTokens = 0;
    }
    
    // If no dumpable tokens, max borrow is the full wBERA in pool
    if (dumpableTokens == 0) {
        return wberaInPool;
    }
    
    // Calculate with improved precision
    uint256 numerator = wberaInPool * tokensInPool * PRECISION;
    uint256 denominator = (tokensInPool + dumpableTokens) * PRECISION;
    uint256 wberaAfterDump = numerator / denominator;
    
    // Maximum borrow is wBERA in pool minus the impact of a token dump
    return wberaInPool > wberaAfterDump ? wberaInPool - wberaAfterDump : 0;
}

OsitoLending Contract: Core Protocol

This contract implements the main protocol functionality:

State Variables

// Protocol state
uint256 public totalWberaSupplied;
uint256 public totalWberaBorrowed;
uint256 public wberaRate;
uint256 public wberaYieldRate;
uint256 public lastWberaRateUpdate;

// Position tracking
uint256 public nextPositionId = 1;
mapping(uint256 => Position) public positions;

// Token tracking
mapping(address => TokenData) public tokenData;
mapping(address => mapping(address => uint256)) public stakedAmount;

Key Functions

The contract enforces borrow limits in all operations:

function borrow(uint256 positionId, uint256 amount) external nonReentrant {
    // Update states with fresh data
    _updateWberaRate();
    _updateTokenState(token);
    _updatePosition(positionId);
    
    // Calculate position's max borrow
    uint256 maxBorrow = calculatePositionMaxBorrow(positionId);
    if (position.borrowedAmount + amount > maxBorrow) {
        revert("OsitoLending: Borrow amount exceeds limit");
    }
    
    // ... rest of function
}

Liquidation Mechanism

Liquidation ensures protocol solvency:

function liquidate(uint256 positionId) external nonReentrant {
    // Update state with fresh data
    _updateWberaRate();
    _updateTokenState(token);
    _updatePosition(positionId);
    
    // Check if position is liquidatable
    uint256 maxBorrow = calculatePositionMaxBorrow(positionId);
    if (position.borrowedAmount <= maxBorrow) {
        revert("OsitoLending: Position not liquidatable");
    }
    
    // ... liquidation logic
    
    // Close the position
    delete positions[positionId];
}

Key Protocol Patterns

The architecture implements several important patterns:

1. Real-Time State Updates

The protocol updates state at key operations to ensure calculations use fresh data:

  • Updates wBERA rate
  • Updates token state
  • Updates position state

2. Precision Management

The protocol uses precision constants (1e18) to maintain accuracy in calculations:

uint256 private constant PRECISION = 1e18;

3. Safety Checks

Key safety checks are implemented throughout:

  • Zero amount checks
  • Token eligibility verification
  • Borrow limit enforcement
  • Liquidation conditions

4. Gas Optimization

The protocol includes gas optimizations:

  • Unchecked blocks where safe
  • Efficient storage patterns
  • Minimal state updates

Protocol Benefits

This architecture provides several benefits:

  1. No Oracle Dependence: Uses on-chain AMM data instead of price feeds
  2. Objective Token Criteria: Token eligibility based on verifiable properties
  3. Mathematical Borrow Limits: Lending limits based on pool state
  4. Efficient Implementation: Gas-optimized with minimal state updates