Skip to content

Instantly share code, notes, and snippets.

@kmjones1979
Last active May 30, 2025 11:51
Show Gist options
  • Save kmjones1979/01b30cb9e0a086b6a3af5b417f0ead4a to your computer and use it in GitHub Desktop.
Save kmjones1979/01b30cb9e0a086b6a3af5b417f0ead4a to your computer and use it in GitHub Desktop.
๐Ÿš€ Stablecoin Challenge Tutorial

๐Ÿš€ Stablecoin Challenge - Complete Developer Tutorial ๐Ÿ’ฐ

Welcome to the most exciting DeFi adventure! ๐ŸŽ‰ Get ready to dive into the world of stablecoins, yield farming, and automated market making!

QR Code

๐Ÿ“š Table of Contents

  1. ๐ŸŒŸ Project Overview
  2. ๐Ÿ—๏ธ Architecture
  3. ๐Ÿ” Smart Contracts Deep Dive
  4. ๐ŸŽจ Frontend Components
  5. ๐Ÿš€ Getting Started
  6. ๐Ÿ’ป Code Examples
  7. ๐Ÿ›ก๏ธ Security Considerations

๐ŸŒŸ Project Overview

QR Code

The Stablecoin Challenge is a comprehensive DeFi protocol that implements a collateralized stablecoin system with the following key features:

  • ๐Ÿ’ต MyUSD: An ETH-collateralized stablecoin that stays stable like a rock!
  • ๐Ÿ“ˆ Staking System: Earn juicy yield on MyUSD deposits ๐Ÿ’ฐ
  • ๐Ÿ”„ DEX Integration: Trade MyUSD/ETH with automated market making magic โœจ
  • โšก Liquidation Engine: Maintain system stability through liquidations (sorry not sorry! ๐Ÿ˜…)
  • ๐ŸŽ›๏ธ Rate Controls: Dynamic interest rate management for maximum profits! ๐Ÿ“Š

๐Ÿ—๏ธ Architecture

Behold the beautiful architecture of our DeFi masterpiece! ๐ŸŽจ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐Ÿ’ต MyUSD Token โ”‚    โ”‚ โš™๏ธ MyUSDEngine   โ”‚    โ”‚ ๐Ÿ“ˆ MyUSDStaking โ”‚
โ”‚   (ERC20) ๐Ÿช™    โ”‚โ—„โ”€โ”€โ”€โ”ค (Core Logic) ๐Ÿง  โ”‚โ—„โ”€โ”€โ”€โ”ค (Yield System)๐Ÿ’ฐโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚                       โ”‚                       โ”‚
         โ”‚                       โ”‚                       โ”‚
         โ–ผ                       โ–ผ                       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   ๐Ÿ”„ DEX        โ”‚    โ”‚  ๐Ÿ“Š Oracle      โ”‚    โ”‚ ๐ŸŽ›๏ธ RateControllerโ”‚
โ”‚ (AMM Trading)๐Ÿ”€ โ”‚    โ”‚ (Price Feed) ๐Ÿ“ˆ โ”‚    โ”‚ (Rate Mgmt) โšก  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ” Smart Contracts Deep Dive

1. ๐Ÿ’ต MyUSD Token Contract

Purpose: ERC20 stablecoin with special handling for staking integration ๐ŸŽฏ

Key Features โœจ:

  • ๐Ÿญ Mintable only by the engine contract (no funny business!)
  • ๐Ÿ”ฅ Burnable by engine and owner (poof! gone!)
  • ๐Ÿ‘ป Virtual balance handling for staking contract (it's like magic!)
  • ๐Ÿ“Š Custom totalSupply() that includes staked tokens

๐Ÿ“‹ State Variables Deep Dive

contract MyUSD is ERC20, Ownable {
    address public stakingContract;  // ๐Ÿ“ˆ Address of the staking contract
    address public engineContract;   // โš™๏ธ Address of the engine contract
}

Variable Explanations:

  • stakingContract ๐Ÿ“ˆ: Points to MyUSDStaking contract for virtual balance calculations
  • engineContract โš™๏ธ: Only this address can mint new MyUSD tokens (maintains collateral backing!)

๐Ÿ—๏ธ Constructor Logic

constructor(address _engineContract, address _stakingContract)
    ERC20("MyUSD", "MyUSD")
    Ownable(msg.sender) {
    engineContract = _engineContract;
    stakingContract = _stakingContract;
}

What happens here ๐ŸŽฏ:

  1. ๐Ÿท๏ธ Sets token name and symbol to "MyUSD"
  2. ๐Ÿ‘‘ Makes deployer the owner
  3. ๐Ÿ”— Links to engine and staking contracts
  4. ๐Ÿš€ Ready to mint some stablecoins!

๐Ÿ”ฅ burnFrom Function

function burnFrom(address account, uint256 amount) external returns (bool) {
    // ๐Ÿ›ก๏ธ Security check: Only engine or owner can burn
    if (msg.sender != engineContract && msg.sender != owner())
        revert MyUSD__NotAuthorized();

    uint256 balance = balanceOf(account);  // ๐Ÿ“Š Get current balance

    // ๐Ÿšซ Validation checks
    if (amount == 0) revert MyUSD__InvalidAmount();
    if (balance < amount) revert MyUSD__InsufficientBalance();

    _burn(account, amount);  // ๐Ÿ”ฅ Burn the tokens
    return true;
}

Step-by-step breakdown ๐Ÿ“:

  1. Authorization Check ๐Ÿ”: Ensures only engine or owner can burn tokens
  2. Balance Retrieval ๐Ÿ“Š: Gets account balance (handles virtual balances for staking!)
  3. Amount Validation โœ…: Prevents burning 0 tokens or more than available
  4. Token Destruction ๐Ÿ”ฅ: Actually burns the tokens from circulation
  5. Success Signal โœ…: Returns true to confirm operation

๐Ÿญ mintTo Function

function mintTo(address to, uint256 amount) external returns (bool) {
    // ๐Ÿ›ก๏ธ CRITICAL: Only engine can mint (maintains collateral backing!)
    if (msg.sender != engineContract) revert MyUSD__NotAuthorized();

    // ๐Ÿšซ Validation checks
    if (to == address(0)) revert MyUSD__InvalidAddress();
    if (amount == 0) revert MyUSD__InvalidAmount();

    _mint(to, amount);  // ๐Ÿญ Create new tokens
    return true;
}

Why only engine can mint ๐Ÿค”:

  • ๐Ÿ’Ž Every MyUSD must be backed by collateral
  • โš™๏ธ Engine ensures proper collateralization before minting
  • ๐Ÿ›ก๏ธ Prevents unauthorized token creation (no infinite money glitch!)

๐Ÿ‘ป balanceOf Function (The Magic!)

function balanceOf(address account) public view override returns (uint256) {
    // ๐Ÿ‘ค For normal accounts, return standard balance
    if (account != stakingContract) {
        return super.balanceOf(account);
    }

    // ๐ŸŽญ For staking contract, return virtual balance
    MyUSDStaking staking = MyUSDStaking(stakingContract);
    return staking.getSharesValue(staking.totalShares());
}

The Virtual Balance Trick ๐ŸŽช:

  1. Normal Users ๐Ÿ‘ค: Get their actual token balance
  2. Staking Contract ๐Ÿ“ˆ: Gets the total value of all staked shares
  3. Why Virtual? ๐Ÿค”: Staking contract doesn't hold actual tokens, just shares that represent value!

๐Ÿ”„ _update Function (Transfer Logic)

function _update(address from, address to, uint256 value) internal override {
    // ๐Ÿ“ˆ If staking contract is sending tokens
    if (from == stakingContract) {
        super._mint(to, value);  // ๐Ÿญ Mint new tokens (virtual โ†’ real)
    }
    // ๐Ÿ“ˆ If sending tokens to staking contract
    else if (to == stakingContract) {
        super._burn(from, value);  // ๐Ÿ”ฅ Burn tokens (real โ†’ virtual)
    }
    // ๐Ÿ‘ค Normal transfer between users
    else {
        super._update(from, to, value);  // ๐Ÿ”„ Standard transfer
    }
}

Transfer Magic Explained โœจ:

  • To Staking ๐Ÿ“ˆ: Burns real tokens, creates virtual shares
  • From Staking ๐Ÿ“‰: Mints real tokens, destroys virtual shares
  • Normal Transfer ๐Ÿ‘ฅ: Standard ERC20 behavior

๐Ÿ“Š totalSupply Function

function totalSupply() public view override returns (uint256) {
    MyUSDStaking staking = MyUSDStaking(stakingContract);
    uint256 stakedTotalSupply = staking.getSharesValue(staking.totalShares());
    return super.totalSupply() + stakedTotalSupply;
}

Total Supply Calculation ๐Ÿงฎ:

  1. Circulating Supply ๐Ÿ”„: Tokens in wallets and contracts
  2. Staked Supply ๐Ÿ“ˆ: Value of all staked shares
  3. True Total ๐Ÿ“Š: Both combined for accurate accounting

Security Features ๐Ÿ›ก๏ธ:

  • ๐Ÿ” Access control for minting/burning (Fort Knox level security!)
  • ๐Ÿšซ Virtual balance prevents double counting (no cheating allowed!)
  • โ›ฝ Custom errors for gas efficiency (save those precious gwei!)

2. โš™๏ธ MyUSDEngine Contract

Purpose: Core protocol logic for collateralization, minting, and liquidations ๐ŸŽฏ

Key Mechanisms ๐Ÿ”ง:

๐Ÿ“‹ State Variables Deep Dive

contract MyUSDEngine is Ownable {
    // ๐Ÿ”ข Constants - The Rules of the Game!
    uint256 private constant COLLATERAL_RATIO = 150;    // 150% minimum collateral
    uint256 private constant LIQUIDATOR_REWARD = 10;    // 10% bonus for liquidators
    uint256 private constant SECONDS_PER_YEAR = 365 days; // Time calculations
    uint256 private constant PRECISION = 1e18;           // 18 decimal precision

    // ๐Ÿ”— Contract References
    MyUSD private i_myUSD;              // The stablecoin contract
    Oracle private i_oracle;            // Price feed oracle
    MyUSDStaking private i_staking;     // Staking contract
    address private i_rateController;   // Rate management contract

    // ๐Ÿ“Š Interest Rate System
    uint256 public borrowRate;          // Annual rate in basis points (100 = 1%)
    uint256 public lastUpdateTime;      // Last interest accrual timestamp
    uint256 public totalDebtShares;     // Total debt shares in circulation
    uint256 public debtExchangeRate;    // Shares to MyUSD conversion rate

    // ๐Ÿ‘ค User Data
    mapping(address => uint256) public s_userCollateral;   // ETH collateral per user
    mapping(address => uint256) public s_userDebtShares;   // Debt shares per user
}

Variable Explanations ๐Ÿ“:

  • COLLATERAL_RATIO ๐Ÿ’Ž: Minimum 150% backing required (safety first!)
  • LIQUIDATOR_REWARD ๐ŸŽ: 10% bonus for liquidators (incentivizes protocol health)
  • borrowRate ๐Ÿ“ˆ: Interest rate borrowers pay (in basis points)
  • debtExchangeRate ๐Ÿ”„: Converts debt shares to actual MyUSD debt (grows with interest!)
  • totalDebtShares ๐Ÿ“Š: Total debt shares across all users
  • s_userCollateral ๐Ÿ’ฐ: Each user's ETH collateral amount
  • s_userDebtShares ๐Ÿ“‹: Each user's debt shares (not actual debt amount!)

๐Ÿ—๏ธ Constructor Logic

constructor(address _oracle, address _myUSDAddress, address _stakingAddress, address _rateController)
    Ownable(msg.sender) {
    i_oracle = Oracle(_oracle);
    i_myUSD = MyUSD(_myUSDAddress);
    i_staking = MyUSDStaking(_stakingAddress);
    i_rateController = _rateController;
    lastUpdateTime = block.timestamp;
    debtExchangeRate = PRECISION; // 1:1 initially (1 share = 1 MyUSD)
}

Setup Process ๐Ÿš€:

  1. ๐Ÿ”— Links all contract dependencies
  2. โฐ Sets initial timestamp for interest calculations
  3. ๐Ÿ“Š Initializes debt exchange rate at 1:1 (no interest yet!)

๐Ÿ’Ž Collateralization System

  • Minimum Ratio: 150% (1.5 ETH value per 1 MyUSD) ๐Ÿ“
  • Liquidation Threshold: Below 150% โš ๏ธ
  • Liquidator Reward: 10% bonus ๐ŸŽ (thanks for keeping us safe!)

๐Ÿ’ฐ addCollateral Function

function addCollateral() public payable {
    if (msg.value == 0) {
        revert Engine__InvalidAmount(); // ๐Ÿšซ No zero deposits!
    }

    s_userCollateral[msg.sender] += msg.value; // ๐Ÿ“ˆ Add to user's collateral
    emit CollateralAdded(msg.sender, msg.value, i_oracle.getPrice()); // ๐Ÿ“ก Broadcast event
}

Step-by-step ๐Ÿ“:

  1. Validation โœ…: Ensures user sent ETH with transaction
  2. Storage Update ๐Ÿ’พ: Adds ETH to user's collateral balance
  3. Event Emission ๐Ÿ“ก: Logs the deposit with current ETH price

๐Ÿ’ธ withdrawCollateral Function

function withdrawCollateral(uint256 amount) external {
    // ๐Ÿšซ Validation checks
    if (amount == 0 || s_userCollateral[msg.sender] < amount) {
        revert Engine__InvalidAmount();
    }

    // ๐Ÿงฎ Temporarily update collateral for safety check
    uint256 newCollateral = s_userCollateral[msg.sender] - amount;
    s_userCollateral[msg.sender] = newCollateral;

    // ๐Ÿ›ก๏ธ Ensure position remains safe after withdrawal
    if (s_userDebtShares[msg.sender] > 0) {
        _validatePosition(msg.sender);
    }

    // ๐Ÿ’ธ Transfer ETH to user
    payable(msg.sender).transfer(amount);
    emit CollateralWithdrawn(msg.sender, msg.sender, amount, i_oracle.getPrice());
}

Safety First Approach ๐Ÿ›ก๏ธ:

  1. Pre-validation โœ…: Checks amount and user balance
  2. Optimistic Update ๐Ÿ”„: Updates collateral first
  3. Safety Check ๐Ÿ›ก๏ธ: Validates position is still safe
  4. Transfer ๐Ÿ’ธ: Sends ETH to user (reverts if position unsafe!)

๐Ÿ“ˆ Interest Rate System Deep Dive

The engine uses a debt shares system for compound interest! ๐Ÿคฏ

๐Ÿ”„ _accrueInterest Function

function _accrueInterest() internal {
    if (totalDebtShares == 0) {
        lastUpdateTime = block.timestamp; // ๐Ÿ“… Update timestamp
        return; // ๐Ÿšช Exit if no debt
    }

    uint256 timeElapsed = block.timestamp - lastUpdateTime;
    if (timeElapsed == 0 || borrowRate == 0) return; // ๐Ÿšซ No time passed or no rate

    // ๐Ÿงฎ Calculate total debt value
    uint256 totalDebtValue = (totalDebtShares * debtExchangeRate) / PRECISION;

    // ๐Ÿ’ฐ Calculate interest owed
    uint256 interest = (totalDebtValue * borrowRate * timeElapsed) / (SECONDS_PER_YEAR * 10000);

    if (interest > 0) {
        // ๐Ÿ“ˆ Increase exchange rate (makes shares worth more MyUSD!)
        debtExchangeRate += (interest * PRECISION) / totalDebtShares;
    }
    lastUpdateTime = block.timestamp; // ๐Ÿ“… Update timestamp
}

Interest Magic Explained โœจ:

  1. Time Check โฐ: Calculates time since last update
  2. Debt Calculation ๐Ÿงฎ: Gets total debt value using current exchange rate
  3. Interest Formula ๐Ÿ“Š: (debt ร— rate ร— time) / (year ร— 10000)
  4. Rate Update ๐Ÿ“ˆ: Increases exchange rate so shares represent more debt!

๐Ÿ” _getUpdatedExchangeRate Function

function _getUpdatedExchangeRate() internal view returns (uint256) {
    uint256 timeElapsed = block.timestamp - lastUpdateTime;
    if (timeElapsed == 0 || borrowRate == 0 || totalDebtShares == 0) {
        return debtExchangeRate; // ๐Ÿ“Š Return current rate
    }

    // ๐Ÿงฎ Calculate interest without updating state
    uint256 totalDebtValue = (totalDebtShares * debtExchangeRate) / PRECISION;
    uint256 interest = (totalDebtValue * borrowRate * timeElapsed) / (SECONDS_PER_YEAR * 10000);

    return debtExchangeRate + (interest * PRECISION) / totalDebtShares; // ๐Ÿ“ˆ Return new rate
}

View Function Magic ๐Ÿ”ฎ:

  • Same calculation as _accrueInterest but read-only
  • Used for getting current debt without updating state
  • Perfect for UI displays and calculations!

๐Ÿ’ต getCurrentDebtValue Function

function getCurrentDebtValue(address user) public view returns (uint256) {
    if (s_userDebtShares[user] == 0) return 0; // ๐Ÿšซ No debt

    uint256 updatedExchangeRate = _getUpdatedExchangeRate(); // ๐Ÿ“Š Get current rate
    return ((s_userDebtShares[user] * updatedExchangeRate) / PRECISION) + 1; // ๐Ÿงฎ Calculate debt
}

Debt Calculation ๐Ÿงฎ:

  • userShares ร— exchangeRate = actualDebt
  • The + 1 prevents rounding errors in user's favor
  • Always returns the current debt including accrued interest!

๐Ÿญ mintMyUSD Function

function mintMyUSD(uint256 mintAmount) public {
    if (mintAmount == 0) {
        revert Engine__InvalidAmount(); // ๐Ÿšซ No zero minting
    }

    // ๐Ÿงฎ Calculate debt shares for this mint amount
    uint256 debtShares = _getMyUSDToShares(mintAmount);

    // ๐Ÿ“ˆ Update user and total debt shares
    s_userDebtShares[msg.sender] += debtShares;
    totalDebtShares += debtShares;

    _validatePosition(msg.sender); // ๐Ÿ›ก๏ธ Ensure position is safe

    bool success = i_myUSD.mintTo(msg.sender, mintAmount); // ๐Ÿญ Mint tokens
    if (!success) {
        revert Engine__MintingFailed();
    }

    emit DebtSharesMinted(msg.sender, mintAmount, debtShares); // ๐Ÿ“ก Log event
}

Minting Process ๐Ÿญ:

  1. Amount Validation โœ…: No zero minting allowed
  2. Share Calculation ๐Ÿงฎ: Converts MyUSD amount to debt shares
  3. State Updates ๐Ÿ“Š: Updates user and total debt shares
  4. Safety Check ๐Ÿ›ก๏ธ: Validates collateralization ratio
  5. Token Creation ๐Ÿญ: Actually mints the MyUSD tokens
  6. Event Logging ๐Ÿ“ก: Records the mint operation

๐Ÿ’ธ repayUpTo Function

function repayUpTo(uint256 amount) public {
    if (amount == 0) {
        revert Engine__InvalidAmount(); // ๐Ÿšซ No zero repayment
    }

    uint256 amountInShares = _getMyUSDToShares(amount); // ๐Ÿงฎ Convert to shares

    // ๐Ÿ” Check if user has enough debt
    if (amountInShares > s_userDebtShares[msg.sender]) {
        // ๐Ÿ“Š Only repay what they owe
        amountInShares = s_userDebtShares[msg.sender];
        amount = getCurrentDebtValue(msg.sender);
    }

    // ๐Ÿ“‰ Reduce debt shares
    s_userDebtShares[msg.sender] -= amountInShares;
    totalDebtShares -= amountInShares;

    bool success = i_myUSD.burnFrom(msg.sender, amount); // ๐Ÿ”ฅ Burn tokens
    if (!success) {
        revert Engine__BurningFailed();
    }

    emit DebtSharesBurned(msg.sender, amount, amountInShares); // ๐Ÿ“ก Log event
}

Repayment Logic ๐Ÿ’ธ:

  1. Amount Validation โœ…: No zero repayments
  2. Share Conversion ๐Ÿงฎ: Converts MyUSD to debt shares
  3. Overpayment Protection ๐Ÿ›ก๏ธ: Prevents paying more than owed
  4. State Updates ๐Ÿ“‰: Reduces user and total debt shares
  5. Token Burning ๐Ÿ”ฅ: Destroys the repaid MyUSD
  6. Event Logging ๐Ÿ“ก: Records the repayment

๐Ÿ›ก๏ธ Position Validation System

๐Ÿ“Š calculateCollateralValue Function

function calculateCollateralValue(address user) public view returns (uint256) {
    uint256 collateralAmount = s_userCollateral[user]; // ๐Ÿ’ฐ Get user's ETH
    return (collateralAmount * i_oracle.getPrice()) / 1e18; // ๐Ÿงฎ Convert to USD value
}

๐Ÿงฎ _calculatePositionRatio Function

function _calculatePositionRatio(address user) private view returns (uint256) {
    uint256 mintedAmount = getCurrentDebtValue(user); // ๐Ÿ’ต Get current debt
    uint256 collateralValue = calculateCollateralValue(user); // ๐Ÿ’ฐ Get collateral value

    if (mintedAmount == 0) return type(uint256).max; // โ™พ๏ธ Infinite ratio if no debt

    return (collateralValue * 1e18) / mintedAmount; // ๐Ÿงฎ Calculate ratio
}

Ratio Calculation ๐Ÿ“Š:

  • collateralValue / debtValue = ratio
  • Ratio of 1.5 (1.5e18) = 150% collateralization
  • Higher ratio = safer position!

๐Ÿ›ก๏ธ _validatePosition Function

function _validatePosition(address user) internal view {
    uint256 positionRatio = _calculatePositionRatio(user);
    if ((positionRatio * 100) < COLLATERAL_RATIO * 1e18) {
        revert Engine__UnsafePositionRatio(); // ๐Ÿšจ Position too risky!
    }
}

Safety Check ๐Ÿ›ก๏ธ:

  • Ensures ratio โ‰ฅ 150%
  • Called after every mint/withdrawal
  • Prevents risky positions!

๐Ÿ“ˆ Interest Rate System

  • Debt Shares: Users receive shares representing their debt ๐ŸŽซ
  • Exchange Rate: Increases over time based on borrow rate ๐Ÿ“Š
  • Compound Interest: Automatically accrued ๐Ÿ”„ (money makes money!)
// Calculate current debt including accrued interest
function getCurrentDebtValue(address user) public view returns (uint256) {
    if (s_userDebtShares[user] == 0) return 0;
    uint256 updatedExchangeRate = _getUpdatedExchangeRate();
    return ((s_userDebtShares[user] * updatedExchangeRate) / PRECISION) + 1;
}

// Accrue interest on total debt
function _accrueInterest() internal {
    uint256 timeElapsed = block.timestamp - lastUpdateTime;
    uint256 totalDebtValue = (totalDebtShares * debtExchangeRate) / PRECISION;
    uint256 interest = (totalDebtValue * borrowRate * timeElapsed) / (SECONDS_PER_YEAR * 10000);

    if (interest > 0) {
        debtExchangeRate += (interest * PRECISION) / totalDebtShares;
    }
    lastUpdateTime = block.timestamp;
}

โšก Liquidation Mechanism (The Cleanup Crew!)

The liquidation system keeps the protocol healthy by removing risky positions! ๐Ÿšจ

๐Ÿ” isLiquidatable Function

function isLiquidatable(address user) public view returns (bool) {
    uint256 positionRatio = _calculatePositionRatio(user);
    return (positionRatio * 100) < COLLATERAL_RATIO * 1e18; // Below 150%
}

Liquidation Check ๐Ÿ”:

  • Calculates user's position ratio
  • Returns true if ratio < 150%
  • Public function so anyone can check!

โšก liquidate Function (The Big One!)

function liquidate(address user) external {
    if (!isLiquidatable(user)) {
        revert Engine__NotLiquidatable(); // ๐Ÿšซ Position is safe
    }

    // ๐Ÿ“Š Get user's financial state
    uint256 userDebtValue = getCurrentDebtValue(user);
    uint256 userCollateral = s_userCollateral[user];
    uint256 collateralValue = calculateCollateralValue(user);

    // ๐Ÿ›ก๏ธ Ensure liquidator has enough MyUSD
    if (i_myUSD.balanceOf(msg.sender) < userDebtValue) {
        revert MyUSD__InsufficientBalance();
    }

    // ๐Ÿ›ก๏ธ Ensure liquidator approved the transfer
    if (i_myUSD.allowance(msg.sender, address(this)) < userDebtValue) {
        revert MyUSD__InsufficientAllowance();
    }

    // ๐Ÿ’ธ Transfer MyUSD from liquidator to engine
    i_myUSD.transferFrom(msg.sender, address(this), userDebtValue);

    // ๐Ÿ”ฅ Burn the MyUSD (removes from circulation)
    i_myUSD.burnFrom(address(this), userDebtValue);

    // ๐Ÿ“‰ Clear user's debt completely
    totalDebtShares -= s_userDebtShares[user];
    s_userDebtShares[user] = 0;

    // ๐Ÿงฎ Calculate liquidator reward
    uint256 collateralPurchased = (userDebtValue * userCollateral) / collateralValue;
    uint256 liquidatorReward = (collateralPurchased * LIQUIDATOR_REWARD) / 100;
    uint256 amountForLiquidator = collateralPurchased + liquidatorReward;

    // ๐Ÿ›ก๏ธ Don't exceed user's collateral
    amountForLiquidator = amountForLiquidator > userCollateral ? userCollateral : amountForLiquidator;

    // ๐Ÿ“‰ Update user's collateral
    s_userCollateral[user] = userCollateral - amountForLiquidator;

    // ๐Ÿ’ฐ Send ETH reward to liquidator
    (bool sent, ) = payable(msg.sender).call{ value: amountForLiquidator }("");
    require(sent, "Failed to send Ether");

    emit Liquidation(user, msg.sender, amountForLiquidator, userDebtValue, i_oracle.getPrice());
}

Liquidation Process โšก:

  1. Safety Check ๐Ÿ›ก๏ธ: Ensures position is actually liquidatable
  2. Financial Assessment ๐Ÿ“Š: Gets user's debt and collateral values
  3. Liquidator Validation โœ…: Ensures liquidator has enough MyUSD and approval
  4. Debt Payment ๐Ÿ’ธ: Transfers MyUSD from liquidator to engine
  5. Token Burning ๐Ÿ”ฅ: Removes MyUSD from circulation
  6. Debt Clearing ๐Ÿ“‰: Zeros out user's debt shares
  7. Reward Calculation ๐Ÿงฎ: Calculates 110% reward for liquidator
  8. Collateral Transfer ๐Ÿ’ฐ: Sends ETH reward to liquidator
  9. Event Logging ๐Ÿ“ก: Records the liquidation

Why Liquidation Works ๐Ÿค”:

  • Liquidator Incentive ๐Ÿ’ฐ: Gets 110% value (10% bonus!)
  • Protocol Safety ๐Ÿ›ก๏ธ: Removes risky positions
  • User Protection ๐Ÿ‘ค: Saves remaining collateral from total loss
  • System Stability โš–๏ธ: Maintains overall health

๐ŸŽ›๏ธ setBorrowRate Function

function setBorrowRate(uint256 newRate) external onlyRateController {
    if (newRate < i_staking.savingsRate()) revert Engine__InvalidBorrowRate();
    _accrueInterest(); // ๐Ÿ“Š Update interest before changing rate
    borrowRate = newRate;
    emit BorrowRateUpdated(newRate);
}

Rate Setting Logic ๐ŸŽ›๏ธ:

  1. Authorization ๐Ÿ”: Only rate controller can call
  2. Rate Validation โœ…: Borrow rate must be โ‰ฅ savings rate
  3. Interest Accrual ๐Ÿ“Š: Updates all existing debt first
  4. Rate Update ๐Ÿ”„: Sets new borrow rate
  5. Event Emission ๐Ÿ“ก: Logs the rate change

Why Borrow Rate โ‰ฅ Savings Rate? ๐Ÿค”:

  • Ensures protocol profitability
  • Borrowers pay more than savers earn
  • Prevents arbitrage opportunities

3. ๐Ÿ“ˆ MyUSDStaking Contract

Purpose: Yield-bearing savings account for MyUSD holders ๐Ÿ’ฐ

Key Features ๐ŸŒŸ:

  • Share-based System: Users receive shares representing their stake ๐ŸŽซ
  • Compound Interest: Automatic yield accrual ๐Ÿ”„ (set it and forget it!)
  • Exchange Rate: Increases over time based on savings rate ๐Ÿ“Š (watch your money grow!)

๐Ÿ“‹ State Variables Deep Dive

contract MyUSDStaking is Ownable, ReentrancyGuard {
    MyUSD public immutable myUSD;        // ๐Ÿ’ต The stablecoin contract
    MyUSDEngine public engine;           // โš™๏ธ The engine contract
    address private i_rateController;    // ๐ŸŽ›๏ธ Rate management contract

    // ๐Ÿ“Š Staking Pool State
    uint256 public totalShares;          // Total shares in the pool
    uint256 public exchangeRate;         // Shares to MyUSD conversion rate
    uint256 public lastUpdateTime;       // Last interest accrual timestamp
    uint256 public savingsRate;          // Annual rate in basis points

    // ๐Ÿ‘ค User Data
    mapping(address => uint256) public userShares; // Each user's share balance

    // ๐Ÿ”ข Constants
    uint256 private constant PRECISION = 1e18;
    uint256 private constant SECONDS_PER_YEAR = 365 days;
}

Variable Explanations ๐Ÿ“:

  • totalShares ๐Ÿ“Š: Total shares across all stakers
  • exchangeRate ๐Ÿ”„: How much MyUSD each share is worth (grows with yield!)
  • savingsRate ๐Ÿ’ฐ: Interest rate stakers earn (in basis points)
  • userShares ๐Ÿ‘ค: Each user's share balance (not MyUSD amount!)
  • PRECISION ๐Ÿ”ข: 18 decimal precision for calculations

๐Ÿ—๏ธ Constructor Logic

constructor(address _myUSD, address _engine, address _rateController) Ownable(msg.sender) {
    myUSD = MyUSD(_myUSD);
    engine = MyUSDEngine(_engine);
    i_rateController = _rateController;
    exchangeRate = PRECISION; // 1:1 initially (1 share = 1 MyUSD)
    lastUpdateTime = block.timestamp;
}

Setup Process ๐Ÿš€:

  1. ๐Ÿ”— Links to MyUSD and Engine contracts
  2. ๐Ÿ“Š Initializes exchange rate at 1:1 (no yield yet!)
  3. โฐ Sets initial timestamp for interest calculations

๐ŸŽ›๏ธ setSavingsRate Function

function setSavingsRate(uint256 newRate) external onlyRateController {
    if (newRate > engine.borrowRate()) revert Staking__InvalidSavingsRate();
    _accrueInterest(); // ๐Ÿ“Š Update interest before changing rate
    savingsRate = newRate;
    emit SavingsRateUpdated(newRate);
}

Rate Setting Logic ๐ŸŽ›๏ธ:

  1. Authorization ๐Ÿ”: Only rate controller can call
  2. Rate Validation โœ…: Savings rate must be โ‰ค borrow rate
  3. Interest Accrual ๐Ÿ“Š: Updates all existing stakes first
  4. Rate Update ๐Ÿ”„: Sets new savings rate
  5. Event Emission ๐Ÿ“ก: Logs the rate change

๐Ÿ“ˆ Interest Accrual System

๐Ÿ”„ _accrueInterest Function

function _accrueInterest() internal {
    if (totalShares == 0) {
        lastUpdateTime = block.timestamp; // ๐Ÿ“… Update timestamp
        return; // ๐Ÿšช Exit if no stakes
    }

    uint256 timeElapsed = block.timestamp - lastUpdateTime;
    if (timeElapsed == 0) return; // ๐Ÿšซ No time passed

    // ๐Ÿงฎ Calculate total staked value
    uint256 totalValue = getSharesValue(totalShares);

    // ๐Ÿ’ฐ Calculate interest earned
    uint256 interest = (totalValue * savingsRate * timeElapsed) / (SECONDS_PER_YEAR * 10000);

    if (interest > 0) {
        // ๐Ÿ“ˆ Increase exchange rate (makes shares worth more MyUSD!)
        exchangeRate += (interest * PRECISION) / totalShares;
    }

    lastUpdateTime = block.timestamp; // ๐Ÿ“… Update timestamp
}

Interest Magic Explained โœจ:

  1. Time Check โฐ: Calculates time since last update
  2. Value Calculation ๐Ÿงฎ: Gets total staked value using current exchange rate
  3. Interest Formula ๐Ÿ“Š: (value ร— rate ร— time) / (year ร— 10000)
  4. Rate Update ๐Ÿ“ˆ: Increases exchange rate so shares represent more MyUSD!

๐Ÿ” _getCurrentExchangeRate Function

function _getCurrentExchangeRate() internal view returns (uint256) {
    if (totalShares == 0) return exchangeRate; // ๐Ÿ“Š Return current rate

    uint256 timeElapsed = block.timestamp - lastUpdateTime;
    if (timeElapsed == 0) return exchangeRate; // ๐Ÿšซ No time passed

    // ๐Ÿงฎ Calculate interest without updating state
    uint256 totalValue = (totalShares * exchangeRate) / PRECISION;
    uint256 interest = (totalValue * savingsRate * timeElapsed) / (SECONDS_PER_YEAR * 10000);

    if (interest == 0) return exchangeRate;

    return exchangeRate + ((interest * PRECISION) / totalShares); // ๐Ÿ“ˆ Return new rate
}

View Function Magic ๐Ÿ”ฎ:

  • Same calculation as _accrueInterest but read-only
  • Used for getting current exchange rate without updating state
  • Perfect for UI displays and calculations!

๐Ÿ’ฐ stake Function

function stake(uint256 amount) external nonReentrant {
    if (amount == 0) revert Staking__InvalidAmount(); // ๐Ÿšซ No zero staking

    // ๐Ÿงฎ Calculate shares based on current exchange rate
    uint256 shares = (amount * PRECISION) / _getCurrentExchangeRate();

    // ๐Ÿ“ˆ Update user and total shares
    userShares[msg.sender] += shares;
    totalShares += shares;

    // ๐Ÿ’ธ Transfer MyUSD to contract
    bool success = myUSD.transferFrom(msg.sender, address(this), amount);
    if (!success) revert Staking__TransferFailed();

    emit Staked(msg.sender, amount, shares); // ๐Ÿ“ก Log event
}

Staking Process ๐Ÿ’ฐ:

  1. Amount Validation โœ…: No zero staking allowed
  2. Share Calculation ๐Ÿงฎ: Converts MyUSD amount to shares using current rate
  3. State Updates ๐Ÿ“Š: Updates user and total shares
  4. Token Transfer ๐Ÿ’ธ: Moves MyUSD from user to contract
  5. Event Logging ๐Ÿ“ก: Records the stake operation

Share Calculation Example ๐Ÿ“Š:

  • If exchange rate is 1.1 (shares worth 10% more)
  • Staking 100 MyUSD gives: 100 ร— 1e18 / 1.1e18 = ~90.9 shares
  • Those shares will be worth more MyUSD over time!

๐Ÿ’ธ withdraw Function

function withdraw(uint256 shareAmount) external nonReentrant {
    if (shareAmount == 0) revert Staking__InvalidAmount(); // ๐Ÿšซ No zero withdrawal
    if (userShares[msg.sender] < shareAmount) revert Staking__InsufficientBalance(); // ๐Ÿšซ Not enough shares
    if (address(engine) == address(0)) revert Staking__EngineNotSet(); // ๐Ÿšซ Engine not set

    // ๐Ÿงฎ Calculate MyUSD amount including yield
    uint256 amount = getSharesValue(shareAmount);

    // ๐Ÿ“‰ Update user's shares
    userShares[msg.sender] -= shareAmount;

    // ๐Ÿ’ธ Transfer MyUSD to user
    bool success = myUSD.transfer(msg.sender, amount);
    if (!success) revert Staking__TransferFailed();

    // ๐Ÿ“‰ Update total shares (after transfer for virtual balance calculation)
    totalShares -= shareAmount;

    emit Withdrawn(msg.sender, amount, shareAmount); // ๐Ÿ“ก Log event
}

Withdrawal Process ๐Ÿ’ธ:

  1. Amount Validation โœ…: No zero withdrawals, sufficient shares
  2. Value Calculation ๐Ÿงฎ: Converts shares to MyUSD using current rate
  3. Share Update ๐Ÿ“‰: Reduces user's shares
  4. Token Transfer ๐Ÿ’ธ: Sends MyUSD to user
  5. Total Update ๐Ÿ“Š: Updates total shares (order matters for virtual balance!)
  6. Event Logging ๐Ÿ“ก: Records the withdrawal

Why Order Matters ๐Ÿค”:

  • MyUSD contract checks totalShares for virtual balance
  • Must update totalShares after transfer for correct calculation!

๐Ÿ” Helper Functions

๐Ÿ“Š getSharesValue Function

function getSharesValue(uint256 shares) public view returns (uint256) {
    return (shares * _getCurrentExchangeRate()) / PRECISION;
}

Value Calculation ๐Ÿ“Š:

  • shares ร— exchangeRate = MyUSD value
  • Shows how much MyUSD the shares are worth
  • Includes all accrued yield!

๐Ÿ’ฐ getBalance Function

function getBalance(address user) external view returns (uint256) {
    if (userShares[user] == 0) return 0;
    return getSharesValue(userShares[user]);
}

User Balance ๐Ÿ‘ค:

  • Returns user's total MyUSD value (shares + yield)
  • Perfect for UI displays
  • Always up-to-date with current exchange rate!

4. ๐Ÿ”„ DEX Contract

Purpose: Automated Market Maker for MyUSD/ETH trading ๐Ÿ”€

AMM Formula: Constant Product (x * y = k) ๐Ÿงฎ (the magic formula!)

๐Ÿ“‹ State Variables Deep Dive

contract DEX {
    IERC20 token;                              // ๐Ÿ’ต The MyUSD token contract
    uint256 public totalLiquidity;             // ๐ŸŒŠ Total liquidity pool tokens
    mapping(address => uint256) public liquidity; // ๐Ÿ‘ค Each user's liquidity tokens
}

Variable Explanations ๐Ÿ“:

  • token ๐Ÿ’ต: Reference to MyUSD token contract
  • totalLiquidity ๐ŸŒŠ: Total liquidity provider tokens in circulation
  • liquidity ๐Ÿ‘ค: Each user's share of the liquidity pool

๐Ÿ—๏ธ Constructor Logic

constructor(address tokenAddr) {
    token = IERC20(tokenAddr); // ๐Ÿ”— Link to MyUSD token
}

Setup Process ๐Ÿš€:

  • Links to the MyUSD token contract
  • Ready to facilitate trading!

๐ŸŒŠ init Function (Pool Initialization)

function init(uint256 tokens) public payable returns (uint256) {
    require(totalLiquidity == 0, "DEX: init - already has liquidity"); // ๐Ÿšซ One-time only

    totalLiquidity = address(this).balance; // ๐Ÿ“Š Set initial liquidity to ETH amount
    liquidity[msg.sender] = totalLiquidity; // ๐Ÿ‘ค Give all LP tokens to initializer

    require(token.transferFrom(msg.sender, address(this), tokens), "DEX: init - transfer did not transact");
    return totalLiquidity;
}

Initialization Process ๐ŸŒŠ:

  1. One-time Check โœ…: Ensures pool isn't already initialized
  2. Liquidity Setting ๐Ÿ“Š: Sets total liquidity to ETH amount sent
  3. Token Assignment ๐Ÿ‘ค: Gives all LP tokens to initializer
  4. Token Transfer ๐Ÿ’ธ: Moves MyUSD tokens to DEX
  5. Return Value ๐Ÿ“Š: Returns amount of LP tokens minted

๐Ÿงฎ AMM Pricing Functions

๐Ÿ“Š price Function (The Core Formula!)

function price(uint256 xInput, uint256 xReserves, uint256 yReserves)
    public pure returns (uint256 yOutput) {
    uint256 numerator = xInput * yReserves;     // ๐Ÿงฎ Input ร— Output Reserve
    uint256 denominator = (xReserves) + xInput; // ๐Ÿงฎ Input Reserve + Input
    return (numerator / denominator);           // ๐Ÿงฎ Final calculation
}

AMM Magic Explained โœจ:

  • Formula: yOutput = (xInput ร— yReserves) / (xReserves + xInput)
  • Constant Product: Maintains x ร— y = k relationship
  • Slippage: Larger trades get worse prices (protects liquidity!)

Example Calculation ๐Ÿ“Š:

  • ETH Reserve: 100 ETH, MyUSD Reserve: 180,000 MyUSD
  • Input: 1 ETH
  • Output: (1 ร— 180,000) / (100 + 1) = 1,782.18 MyUSD
  • Price: ~1,782 MyUSD per ETH

๐Ÿ’ฑ currentPrice Function

function currentPrice() public view returns (uint256 _currentPrice) {
    _currentPrice = price(1 ether, address(this).balance, token.balanceOf(address(this)));
}

Current Price Calculation ๐Ÿ’ฑ:

  • Calculates price for 1 ETH in MyUSD
  • Uses current pool reserves
  • Perfect for price displays!

๐Ÿ”„ Trading Functions

๐Ÿ’ฐ ethToToken Function

function ethToToken() internal returns (uint256 tokenOutput) {
    require(msg.value > 0, "cannot swap 0 ETH"); // ๐Ÿšซ No zero swaps

    uint256 ethReserve = address(this).balance - msg.value; // ๐Ÿ“Š Reserve before swap
    uint256 tokenReserve = token.balanceOf(address(this));  // ๐Ÿ“Š Token reserve

    tokenOutput = price(msg.value, ethReserve, tokenReserve); // ๐Ÿงฎ Calculate output

    require(token.transfer(msg.sender, tokenOutput), "ethToToken(): reverted swap.");
    emit Swap(msg.sender, address(0), msg.value, address(token), tokenOutput);
    return tokenOutput;
}

ETH โ†’ MyUSD Process ๐Ÿ’ฐ:

  1. Validation โœ…: No zero ETH swaps
  2. Reserve Calculation ๐Ÿ“Š: Gets reserves before adding new ETH
  3. Price Calculation ๐Ÿงฎ: Uses AMM formula for output amount
  4. Token Transfer ๐Ÿ’ธ: Sends MyUSD to user
  5. Event Emission ๐Ÿ“ก: Logs the swap

๐Ÿ’ธ tokenToEth Function

function tokenToEth(uint256 tokenInput) internal returns (uint256 ethOutput) {
    require(tokenInput > 0, "cannot swap 0 tokens"); // ๐Ÿšซ No zero swaps
    require(token.balanceOf(msg.sender) >= tokenInput, "insufficient token balance"); // ๐Ÿšซ Check balance
    require(token.allowance(msg.sender, address(this)) >= tokenInput, "insufficient allowance"); // ๐Ÿšซ Check approval

    uint256 tokenReserve = token.balanceOf(address(this)); // ๐Ÿ“Š Current token reserve
    ethOutput = price(tokenInput, tokenReserve, address(this).balance); // ๐Ÿงฎ Calculate ETH output

    require(token.transferFrom(msg.sender, address(this), tokenInput), "tokenToEth(): reverted swap.");
    (bool sent,) = msg.sender.call{value: ethOutput}("");
    require(sent, "tokenToEth: revert in transferring eth to you!");

    emit Swap(msg.sender, address(token), tokenInput, address(0), ethOutput);
    return ethOutput;
}

MyUSD โ†’ ETH Process ๐Ÿ’ธ:

  1. Validation โœ…: No zero swaps, sufficient balance and approval
  2. Reserve Check ๐Ÿ“Š: Gets current token reserve
  3. Price Calculation ๐Ÿงฎ: Uses AMM formula for ETH output
  4. Token Transfer ๐Ÿ’ธ: Takes MyUSD from user
  5. ETH Transfer ๐Ÿ’ฐ: Sends ETH to user
  6. Event Emission ๐Ÿ“ก: Logs the swap

๐Ÿ”„ swap Function (Public Interface)

function swap(uint256 inputAmount) public payable returns (uint256 outputAmount) {
    if (msg.value > 0 && inputAmount == msg.value) {
        outputAmount = ethToToken(); // ๐Ÿ’ฐ ETH โ†’ MyUSD
    } else {
        outputAmount = tokenToEth(inputAmount); // ๐Ÿ’ธ MyUSD โ†’ ETH
    }
    emit PriceUpdated(currentPrice()); // ๐Ÿ“Š Update price after swap
}

Smart Swap Logic ๐Ÿ”„:

  • ETH Input: If msg.value > 0, swap ETH for MyUSD
  • Token Input: Otherwise, swap MyUSD for ETH
  • Price Update: Emits new price after swap

๐ŸŒŠ Liquidity Provision Functions

๐Ÿ’ฐ deposit Function

function deposit() public payable returns (uint256 tokensDeposited) {
    require(msg.value > 0, "Must send value when depositing"); // ๐Ÿšซ Must send ETH

    uint256 ethReserve = address(this).balance - msg.value; // ๐Ÿ“Š ETH reserve before deposit
    uint256 tokenReserve = token.balanceOf(address(this));  // ๐Ÿ“Š Token reserve

    // ๐Ÿงฎ Calculate proportional token amount needed
    uint256 tokenDeposit = (msg.value * tokenReserve / ethReserve) + 1;

    require(token.balanceOf(msg.sender) >= tokenDeposit, "insufficient token balance");
    require(token.allowance(msg.sender, address(this)) >= tokenDeposit, "insufficient allowance");

    // ๐Ÿงฎ Calculate LP tokens to mint
    uint256 liquidityMinted = msg.value * totalLiquidity / ethReserve;
    liquidity[msg.sender] += liquidityMinted; // ๐Ÿ‘ค Add to user's LP tokens
    totalLiquidity += liquidityMinted;        // ๐Ÿ“Š Add to total LP tokens

    require(token.transferFrom(msg.sender, address(this), tokenDeposit));
    emit LiquidityProvided(msg.sender, liquidityMinted, msg.value, tokenDeposit);
    return tokenDeposit;
}

Liquidity Provision Process ๐ŸŒŠ:

  1. ETH Validation โœ…: Must send ETH with transaction
  2. Reserve Calculation ๐Ÿ“Š: Gets current reserves
  3. Proportional Calculation ๐Ÿงฎ: Calculates required MyUSD amount
  4. Balance Checks โœ…: Ensures user has enough tokens and approval
  5. LP Token Minting ๐Ÿญ: Calculates and mints LP tokens proportionally
  6. Token Transfer ๐Ÿ’ธ: Takes MyUSD from user
  7. Event Emission ๐Ÿ“ก: Logs the liquidity provision

LP Token Formula ๐Ÿงฎ:

  • liquidityMinted = (ethDeposited ร— totalLiquidity) / ethReserve
  • Proportional to existing pool size
  • Maintains fair share distribution!

๐Ÿ’ธ withdraw Function

function withdraw(uint256 amount) public returns (uint256 ethAmount, uint256 tokenAmount) {
    require(liquidity[msg.sender] >= amount, "withdraw: sender does not have enough liquidity to withdraw.");

    uint256 ethReserve = address(this).balance;   // ๐Ÿ“Š Current ETH reserve
    uint256 tokenReserve = token.balanceOf(address(this)); // ๐Ÿ“Š Current token reserve

    // ๐Ÿงฎ Calculate proportional withdrawals
    uint256 ethWithdrawn = amount * ethReserve / totalLiquidity;
    tokenAmount = amount * tokenReserve / totalLiquidity;

    // ๐Ÿ“‰ Update LP token balances
    liquidity[msg.sender] -= amount;
    totalLiquidity -= amount;

    // ๐Ÿ’ธ Transfer assets to user
    (bool sent,) = payable(msg.sender).call{value: ethWithdrawn}("");
    require(sent, "withdraw(): revert in transferring eth to you!");
    require(token.transfer(msg.sender, tokenAmount));

    emit LiquidityRemoved(msg.sender, amount, tokenAmount, ethWithdrawn);
    return (ethWithdrawn, tokenAmount);
}

Withdrawal Process ๐Ÿ’ธ:

  1. LP Token Check โœ…: Ensures user has enough LP tokens
  2. Reserve Calculation ๐Ÿ“Š: Gets current pool reserves
  3. Proportional Calculation ๐Ÿงฎ: Calculates fair share of both assets
  4. LP Token Burning ๐Ÿ”ฅ: Reduces user and total LP tokens
  5. Asset Transfer ๐Ÿ’ฐ: Sends ETH and MyUSD to user
  6. Event Emission ๐Ÿ“ก: Logs the withdrawal

Withdrawal Formula ๐Ÿงฎ:

  • ethWithdrawn = (lpTokens ร— ethReserve) / totalLiquidity
  • tokensWithdrawn = (lpTokens ร— tokenReserve) / totalLiquidity
  • Fair proportional distribution!

๐Ÿงฎ calculateXInput Function (Reverse Calculation)

function calculateXInput(uint256 yOutput, uint256 xReserves, uint256 yReserves)
    public pure returns (uint256 xInput) {
    uint256 numerator = yOutput * xReserves;   // ๐Ÿงฎ Output ร— Input Reserve
    uint256 denominator = yReserves - yOutput; // ๐Ÿงฎ Output Reserve - Output
    return (numerator / denominator) + 1;      // ๐Ÿงฎ Add 1 for rounding
}

Reverse AMM Calculation ๐Ÿ”„:

  • Given desired output, calculates required input
  • Formula: xInput = (yOutput ร— xReserves) / (yReserves - yOutput)
  • Useful for exact output swaps!

5. ๐Ÿ“Š Oracle Contract

Purpose: Price feed for ETH/USD conversion ๐Ÿ’ฑ (the crystal ball of prices!)

๐Ÿ“‹ State Variables Deep Dive

contract Oracle {
    DEX public dexAddress; // ๐Ÿ”„ Reference to the DEX contract
}

Variable Explanations ๐Ÿ“:

  • dexAddress ๐Ÿ”„: Points to DEX contract for price data

๐Ÿ—๏ธ Constructor Logic

constructor(address _dexAddress) {
    dexAddress = DEX(_dexAddress); // ๐Ÿ”— Link to DEX contract
}

Setup Process ๐Ÿš€:

  • Links to the DEX contract for price feeds
  • Ready to provide price data!

๐Ÿ’ฑ getPrice Function (The Oracle Speaks!)

function getPrice() public view returns (uint256) {
    // ๐Ÿ“Š Get price from DEX
    uint256 _price = dexAddress.currentPrice();

    if (_price == 0) {
        _price = 1800 ether; // ๐Ÿ”„ Default fallback price
    }
    return _price;
}

Price Oracle Logic ๐Ÿ’ฑ:

  1. DEX Price Query ๐Ÿ“Š: Gets current price from DEX
  2. Fallback Protection ๐Ÿ›ก๏ธ: Uses default price if DEX has no liquidity
  3. Price Return ๐Ÿ’ฐ: Returns ETH price in MyUSD terms

Why This Design? ๐Ÿค”:

  • Decentralized: Uses DEX price instead of external oracle
  • Resilient: Has fallback price for edge cases
  • Simple: Minimal complexity for demo purposes
  • Real-world: Production would use Chainlink or similar!

6. ๐ŸŽ›๏ธ RateController Contract

Purpose: Centralized rate management for borrowing and savings โšก (the puppet master!)

๐Ÿ“‹ State Variables Deep Dive

contract RateController {
    MyUSDEngine private i_myUSD;  // โš™๏ธ Reference to engine contract
    MyUSDStaking private i_staking; // ๐Ÿ“ˆ Reference to staking contract
}

Variable Explanations ๐Ÿ“:

  • i_myUSD โš™๏ธ: Points to MyUSDEngine for borrow rate control
  • i_staking ๐Ÿ“ˆ: Points to MyUSDStaking for savings rate control

๐Ÿ—๏ธ Constructor Logic

constructor(address _myUSD, address _staking) {
    i_myUSD = MyUSDEngine(_myUSD);     // ๐Ÿ”— Link to engine
    i_staking = MyUSDStaking(_staking); // ๐Ÿ”— Link to staking
}

Setup Process ๐Ÿš€:

  • Links to both engine and staking contracts
  • Ready to control interest rates!

๐Ÿ“ˆ setBorrowRate Function

function setBorrowRate(uint256 newRate) external {
    i_myUSD.setBorrowRate(newRate); // โš™๏ธ Update engine's borrow rate
}

Borrow Rate Control ๐Ÿ“ˆ:

  • Direct Control: Calls engine's rate setting function
  • No Restrictions: Anyone can call (in this demo!)
  • Immediate Effect: Updates rate instantly

๐Ÿ’ฐ setSavingsRate Function

function setSavingsRate(uint256 newRate) external {
    i_staking.setSavingsRate(newRate); // ๐Ÿ“ˆ Update staking's savings rate
}

Savings Rate Control ๐Ÿ’ฐ:

  • Direct Control: Calls staking's rate setting function
  • No Restrictions: Anyone can call (in this demo!)
  • Immediate Effect: Updates rate instantly

Why Separate Controller? ๐Ÿค”:

  • Centralization: Single point for rate management
  • Flexibility: Easy to add governance or restrictions
  • Separation of Concerns: Keeps rate logic separate from core contracts
  • Future Upgrades: Can add complex rate algorithms later!

Production Considerations ๐Ÿญ:

  • Add access control (only governance can call)
  • Implement rate change limits
  • Add time delays for security
  • Include rate validation logic

๐ŸŽจ Frontend Components

Time to make it pretty! Let's explore the UI magic โœจ

๐ŸŽฏ Core UI Components

1. ๐Ÿ’ฐ TokenActions Component

Purpose: MyUSD wallet interface with transfer and swap functionality ๐Ÿ”„

const TokenActions = () => {
    const { data: stablecoinBalance } = useScaffoldReadContract({
        contractName: "MyUSD",
        functionName: "balanceOf",
        args: [address],
    });

    const { data: ethPrice } = useScaffoldReadContract({
        contractName: "Oracle",
        functionName: "getPrice",
    });

    const myUSDPrice = 1 / (Number(formatEther(ethPrice || 0n)) / 1800);

    return (
        <div className="absolute mt-10 right-0 bg-base-100 w-fit border-base-300 border shadow-md rounded-xl z-10">
            {/* Wallet display and action buttons */}
        </div>
    );
};

2. ๐ŸŽ›๏ธ RateControls Component

Purpose: Interface for adjusting borrow and savings rates ๐Ÿ“Š (control the money flow!)

const RateControls: React.FC = () => {
    const { data: savingsRate } = useScaffoldReadContract({
        contractName: "MyUSDStaking",
        functionName: "savingsRate",
    });

    const { data: borrowRate } = useScaffoldReadContract({
        contractName: "MyUSDEngine",
        functionName: "borrowRate",
    });

    const { writeContractAsync: writeRateControllerContractAsync } =
        useScaffoldWriteContract({
            contractName: "RateController",
        });

    const handleSaveBorrowRate = useCallback(
        async (value: string) => {
            await writeRateControllerContractAsync({
                functionName: "setBorrowRate",
                args: [BigInt(Math.round(Number(value) * 100))],
            });
        },
        [writeRateControllerContractAsync]
    );

    // UI for rate editing
};

3. ๐Ÿ“Š UserPositionsTable Component

Purpose: Display user's collateral and debt positions ๐Ÿ’Ž (know your worth!)

4. ๐Ÿ† StakersTable Component

Purpose: Show staking positions and yields ๐Ÿ’ฐ (leaderboard of earners!)

5. ๐Ÿ“ˆ PriceGraph Component

Purpose: Real-time price visualization ๐Ÿ“Š (watch the magic happen!)

6. ๐Ÿ“‰ SupplyGraph Component

Purpose: MyUSD supply metrics visualization ๐Ÿ“Š (see the big picture!)

๐Ÿš€ Getting Started

Ready to launch? Let's get this rocket ship moving! ๐Ÿš€

๐Ÿ“‹ Prerequisites

# Install dependencies ๐Ÿ“ฆ
yarn install

# Start local blockchain โ›“๏ธ
yarn chain

# Deploy contracts ๐Ÿš€
yarn deploy

# Start frontend ๐ŸŽจ
yarn start

๐ŸŽฏ Basic Usage Flow

Time to make some money! ๐Ÿ’ฐ

  1. ๐Ÿ’Ž Add Collateral (Put your ETH to work!):
// Add ETH collateral
const { writeContractAsync } = useScaffoldWriteContract({
    contractName: "MyUSDEngine",
});

await writeContractAsync({
    functionName: "addCollateral",
    value: parseEther("1.0"), // 1 ETH
});
  1. ๐Ÿญ Mint MyUSD (Create money from thin air!):
// Mint MyUSD against collateral
await writeContractAsync({
    functionName: "mintMyUSD",
    args: [parseEther("1000")], // 1000 MyUSD
});
  1. ๐Ÿ“ˆ Stake for Yield (Let your money work while you sleep!):
// First approve staking contract
await writeContractAsync({
    contractName: "MyUSD",
    functionName: "approve",
    args: [stakingContractAddress, parseEther("1000")],
});

// Then stake
await writeContractAsync({
    contractName: "MyUSDStaking",
    functionName: "stake",
    args: [parseEther("1000")],
});
  1. ๐Ÿ”„ Trade on DEX (Be your own market maker!):
// Swap ETH for MyUSD
await writeContractAsync({
    contractName: "DEX",
    functionName: "swap",
    args: [parseEther("0.5")],
    value: parseEther("0.5"),
});

๐Ÿ’ป Code Examples

Time to get our hands dirty with some code! ๐Ÿ‘จโ€๐Ÿ’ป

๐ŸŽข Complete User Journey

From zero to DeFi hero in 4 easy steps! ๐Ÿฆธโ€โ™‚๏ธ

// 1. Connect wallet and check balances
const { address } = useAccount();
const { data: ethBalance } = useBalance({ address });
const { data: myUSDBalance } = useScaffoldReadContract({
    contractName: "MyUSD",
    functionName: "balanceOf",
    args: [address],
});

// 2. Add collateral and mint MyUSD
const addCollateralAndMint = async () => {
    try {
        // Add 2 ETH as collateral
        await writeContractAsync({
            contractName: "MyUSDEngine",
            functionName: "addCollateral",
            value: parseEther("2.0"),
        });

        // Mint 2000 MyUSD (safe ratio: 150%)
        await writeContractAsync({
            contractName: "MyUSDEngine",
            functionName: "mintMyUSD",
            args: [parseEther("2000")],
        });
    } catch (error) {
        console.error("Transaction failed:", error);
    }
};

// 3. Stake MyUSD for yield
const stakeMyUSD = async (amount: string) => {
    try {
        // Approve staking contract
        await writeContractAsync({
            contractName: "MyUSD",
            functionName: "approve",
            args: [stakingContractAddress, parseEther(amount)],
        });

        // Stake tokens
        await writeContractAsync({
            contractName: "MyUSDStaking",
            functionName: "stake",
            args: [parseEther(amount)],
        });
    } catch (error) {
        console.error("Staking failed:", error);
    }
};

// 4. Monitor position health
const { data: isLiquidatable } = useScaffoldReadContract({
    contractName: "MyUSDEngine",
    functionName: "isLiquidatable",
    args: [address],
});

const { data: currentDebt } = useScaffoldReadContract({
    contractName: "MyUSDEngine",
    functionName: "getCurrentDebtValue",
    args: [address],
});

const { data: collateralValue } = useScaffoldReadContract({
    contractName: "MyUSDEngine",
    functionName: "calculateCollateralValue",
    args: [address],
});

๐Ÿš€ Advanced Integration Examples

Ready for some advanced wizardry? ๐Ÿง™โ€โ™‚๏ธ

๐Ÿค– Custom Liquidation Bot

Be the hero that saves the protocol! ๐Ÿฆธโ€โ™‚๏ธ

const liquidationBot = async () => {
    // Monitor all positions
    const positions = await getAllPositions();

    for (const position of positions) {
        const isLiquidatable = await myUSDEngine.isLiquidatable(position.user);

        if (isLiquidatable) {
            const debtValue = await myUSDEngine.getCurrentDebtValue(
                position.user
            );
            const myUSDBalance = await myUSD.balanceOf(botAddress);

            if (myUSDBalance >= debtValue) {
                // Approve and liquidate
                await myUSD.approve(engineAddress, debtValue);
                await myUSDEngine.liquidate(position.user);
                console.log(`Liquidated position: ${position.user}`);
            }
        }
    }
};

๐ŸŒพ Yield Farming Strategy

Time to farm some serious yield! ๐Ÿšœ๐Ÿ’ฐ

const yieldFarmingStrategy = async () => {
    // 1. Check current rates
    const borrowRate = await myUSDEngine.borrowRate();
    const savingsRate = await myUSDStaking.savingsRate();
    const spread = savingsRate - borrowRate;

    if (spread > 200) {
        // 2% spread minimum
        // 2. Add collateral
        await myUSDEngine.addCollateral({ value: parseEther("10") });

        // 3. Mint MyUSD at borrow rate
        await myUSDEngine.mintMyUSD(parseEther("6000"));

        // 4. Stake at savings rate
        await myUSD.approve(stakingAddress, parseEther("6000"));
        await myUSDStaking.stake(parseEther("6000"));

        console.log(`Yield farming active with ${spread / 100}% spread`);
    }
};

๐Ÿ›ก๏ธ Security Considerations

Safety first! Let's keep those funds SAFU! ๐Ÿ”’

๐Ÿ” Smart Contract Security

  1. ๐Ÿ”„ Reentrancy Protection:

    • All external calls use ReentrancyGuard ๐Ÿ›ก๏ธ
    • Checks-Effects-Interactions pattern followed โœ…
  2. ๐Ÿ” Access Control:

    • Only engine can mint MyUSD ๐Ÿญ
    • Only rate controller can set rates ๐ŸŽ›๏ธ
    • Owner controls for emergency functions ๐Ÿšจ
  3. ๐Ÿ”ข Integer Overflow Protection:

    • Solidity 0.8+ built-in protection ๐Ÿ›ก๏ธ
    • Custom errors for gas efficiency โ›ฝ
  4. ๐Ÿ“Š Price Oracle Security:

    • Fallback price mechanism ๐Ÿ”„
    • DEX-based pricing with safeguards ๐Ÿ›ก๏ธ

๐ŸŽจ Frontend Security

  1. Input Validation:
const validateAmount = (amount: string): boolean => {
    const num = Number(amount);
    return num > 0 && num <= MAX_SAFE_AMOUNT && !isNaN(num);
};
  1. Transaction Safety:
const safeTransactionCall = async (contractCall: () => Promise<any>) => {
    try {
        const result = await contractCall();
        await result.wait(); // Wait for confirmation
        return result;
    } catch (error) {
        console.error("Transaction failed:", error);
        throw error;
    }
};
  1. State Management:
    • Real-time balance updates
    • Position health monitoring
    • Error handling and user feedback

๐ŸŽฏ Best Practices

  1. Always check position health before operations ๐Ÿฅ
  2. Use slippage protection for DEX trades ๐Ÿ›ก๏ธ
  3. Monitor liquidation risk continuously ๐Ÿ‘€
  4. Implement proper error handling ๐Ÿšจ
  5. Use events for off-chain monitoring ๐Ÿ“ก
  6. Test all edge cases thoroughly ๐Ÿงช

๐ŸŽ‰ Conclusion

The Stablecoin Challenge demonstrates a complete DeFi protocol with:

  • ๐Ÿ’ช Robust collateralization mechanics
  • ๐Ÿ’ฐ Yield-bearing staking system
  • ๐ŸŒŠ Integrated DEX for liquidity
  • ๐ŸŽ›๏ธ Dynamic rate management
  • โšก Comprehensive liquidation system

This tutorial provides the foundation for understanding and extending the protocol. The modular architecture allows for easy customization and additional features. ๐Ÿš€

For further development, consider:

  • ๐Ÿ—๏ธ Multi-collateral support
  • ๐Ÿ—ณ๏ธ Governance mechanisms
  • ๐Ÿ“ˆ Advanced yield strategies
  • ๐ŸŒ‰ Cross-chain integration
  • ๐Ÿ”ฎ Enhanced oracle systems

Happy coding, DeFi builders! ๐ŸŽ‰๐Ÿ’ปโœจ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment