Skip to content

SovaNetwork/fountfi-open

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fountfi Protocol Technical Documentation

Fountfi is a protocol for tokenizing Real World Assets (RWA) on-chain, providing a secure and compliant framework for bringing traditional finance assets into DeFi.

Deployments

Optimism Sepolia - Multi-Collateral Deployment [2025-08-02]

Network: Optimism Sepolia (Chain ID: 11155420)

Deployer: 0x0670faf0016E1bf591fEd8e0322689E894104F81

Contract Address
Role Manager 0xE401DF98cA0e73371111A76AAbC067a84Eda1f7D
Registry 0xcBBf02D619F4C53B1826B64Cc07Ea39Fb8442f13
Conduit 0x2BE0922fC3732b840d42A8912F0296321D9C4a2E
MultiCollateral Registry 0x63c8215a478f8F57C548a8700420Ac5Bd8Dc3749
Strategy 0xe7Ced7592F323a798A3aF6Cb3E041A9a7179F9A4
Vault (tRWA) 0x2b82b75A0bF1AA01C9474546904bb446FD4E75C7
SovaBTC 0x7CAAC5eB64E3721a82121f3b9b247Cb6fFca7203
WBTC 0xc9FE3e6fF20fE4EB4F48B3993C947be51007D2C1
tBTC 0x0b00093Dcc35c1a887789e079453c64b641872b6
cbBTC 0xfa4f9504B0f922221c209B8aC56294A31bC22618

Base V1 Deployment [2025-07-07]

Git Tag: deploy-20250707

Audit: Omniscia Audit Report

Deployer: 0x76F2DAD4741CB0f4C8C56361d8cF5E05Bc01Bf28

Contract Address
Role Manager 0x6c826Ea5664d64F570ed50d82107593c33af8A47
Registry 0x50f4EF3B29362abA1B9EA88E600B016d99c44C3E
Conduit 0x66Df6ECcC14D8bd12249b587835D92125e9DBe71
KYC Rules Hook 0xD1b225374c64C87Cf0909910AF34e54021EFaEaA
Price Oracle Reporter 0xB8bBabD3134C18cA705dA904A73A86CB1001955a
Strategy Implementation 0x66CA4C7973A73fd243563D068452F8D1C2D1E123
Mock USD Token 0x295F16c13feA14c55289d16D83b2ABAAD3B820f3

Multi-Collateral System Overview

The Fountfi protocol now supports multiple Bitcoin-pegged tokens as collateral, all standardized to SovaBTC for accounting purposes.

Multi-Collateral Flow Diagram

graph TB
    subgraph "Users"
        U1[User with WBTC]
        U2[User with tBTC]
        U3[User with cbBTC]
        U4[User with SovaBTC]
    end

    subgraph "Multi-Collateral Infrastructure"
        MCR[MultiCollateral Registry<br/>- Manages allowed tokens<br/>- Tracks conversion rates<br/>- Converts to/from SovaBTC]
        COND[Conduit<br/>- Collects any allowed token<br/>- Routes to strategy]
    end

    subgraph "Vault & Strategy"
        VAULT[tRWA Vault<br/>- ERC4626 shares<br/>- Multi-collateral aware<br/>- Returns SovaBTC]
        STRAT[MultiCollateral Strategy<br/>- Holds all collateral types<br/>- Tracks balances per token<br/>- Values in SovaBTC terms]
    end

    subgraph "Collateral Tokens"
        WBTC[WBTC<br/>8 decimals]
        TBTC[tBTC<br/>18 decimals]
        CBBTC[cbBTC<br/>8 decimals]
        SBTC[SovaBTC<br/>8 decimals<br/>(Standard unit)]
    end

    %% User deposits
    U1 -->|Deposit| WBTC
    U2 -->|Deposit| TBTC
    U3 -->|Deposit| CBBTC
    U4 -->|Deposit| SBTC

    %% Token approvals
    WBTC -->|Approve| COND
    TBTC -->|Approve| COND
    CBBTC -->|Approve| COND
    SBTC -->|Approve| COND

    %% Conduit flow
    COND -->|collectTokens| VAULT
    VAULT -->|Check rates| MCR
    MCR -->|Verify allowed| COND

    %% Strategy receives tokens
    VAULT -->|Forward tokens| STRAT
    STRAT -->|Track balances| STRAT

    %% Valuation flow
    STRAT -->|Query rates| MCR
    MCR -->|Return SovaBTC value| STRAT

    %% Withdrawal flow
    VAULT -.->|Withdraw shares| U1
    STRAT -.->|Return SovaBTC| VAULT

    style MCR fill:#f9f,stroke:#333,stroke-width:2px
    style VAULT fill:#bbf,stroke:#333,stroke-width:2px
    style SBTC fill:#ffd700,stroke:#333,stroke-width:2px
Loading

Key Terms and Concepts

  • tRWA (Tokenized Real World Asset): An ERC4626-compatible token that represents ownership shares in an underlying real-world asset strategy.

  • Multi-Collateral Support: The protocol now accepts multiple Bitcoin-pegged tokens (WBTC, tBTC, cbBTC) as collateral, all valued in SovaBTC terms.

  • SovaBTC: The standard unit of account for all Bitcoin collateral in the protocol. All deposits are valued and withdrawals are made in SovaBTC.

  • MultiCollateral Registry: A new component that manages allowed collateral tokens and their conversion rates to SovaBTC, handling decimal differences between tokens.

  • Collateral Conversion: The protocol automatically converts between different Bitcoin-pegged tokens and SovaBTC based on configured exchange rates.

  • Strategy: A contract that manages underlying assets and deploys its own tRWA token. Strategies implement different investment approaches for the underlying assets.

  • Registry: The central hub of the protocol that manages all component registrations and deployments, ensuring only authorized components can interact.

  • Conduit: A security contract that manages all asset transfers between users and the protocol, providing a layer of protection for token movements.

  • Hook: A pluggable validation component that enforces rules before token operations (deposits, withdrawals, transfers). Hooks enable extensible compliance mechanisms.

  • Rules Engine: A special type of hook that coordinates multiple sub-hooks, allowing complex validation logic to be composed from simpler rules.

  • Reporter: An oracle contract that provides asset valuations to the protocol, enabling accurate share pricing.

  • GatedMint: A two-phase deposit mechanism where assets are held in escrow pending explicit approval, allowing for issuer control over flow of funds and timing of acceptance.

  • Deposit Escrow: A contract that temporarily holds assets during the two-phase deposit process until they are either accepted or refunded.

  • Withdrawal Queue: A mechanism for managing withdrawals from illiquid assets, allowing for orderly processing of redemption requests.

Protocol Overview

The Fountfi protocol enables:

  • Tokenization of RWAs with flexible strategy implementations
  • Share-based accounting for RWA representation using ERC4626 vaults
  • NAV (Net Asset Value) oracle integration via Reporter contracts
  • Built-in KYC/AML compliance features with extensible hook system
  • Two-phase gated deposit flow for compliance verification
  • Advanced withdrawal management for illiquid assets
  • Role-based access control for protocol administration

Protocol Architecture

Fountfi Protocol Architecture

Core Components

Registry

Central hub of the protocol that manages all component registrations and deployments.

Key Functions:

// Register/unregister a strategy implementation template
function setStrategy(address implementation, bool allowed) external;

// Register/unregister a hook implementation
function setHook(address implementation, bool allowed) external;

// Register/unregister an asset token (e.g., USDC)
function setAsset(address asset, bool allowed) external;

// Deploy a new strategy instance and its token
function deploy(
    address implementation,
    string memory name,
    string memory symbol,
    address asset,
    uint8 assetDecimals,
    address manager,
    bytes memory initData
) external returns (address strategy, address token);

// Check if a token is a valid tRWA token
function isToken(address token) external view returns (bool);

// Get all deployed tokens
function allTokens() external view returns (address[] memory tokens);

// Get all deployed strategies
function allStrategies() external view returns (address[] memory strategies);

Access Control:

  • setStrategy: Requires STRATEGY_ADMIN role
  • setHook: Requires RULES_ADMIN role
  • setAsset: Requires PROTOCOL_ADMIN role
  • deploy: Requires STRATEGY_OPERATOR role

Error Conditions:

  • ZeroAddress(): Implementation address is zero
  • UnauthorizedAsset(): Asset is not registered
  • UnauthorizedStrategy(): Strategy is not registered

Strategy

Investment vehicle component that manages assets and deploys its token.

Types of Strategies:

  1. BasicStrategy: Abstract base implementation with core functionality
  2. ReportedStrategy: Uses an oracle reporter for asset valuation
  3. GatedMintRWAStrategy: Implements two-phase deposit with approval flow

Key Functions (BasicStrategy):

// Initialize a new strategy
function initialize(
    string calldata name_,
    string calldata symbol_,
    address roleManager_,
    address manager_,
    address asset_,
    uint8 assetDecimals_,
    bytes memory
) public virtual;

// Get the total asset balance (abstract, implemented by derived strategies)
function balance() external view virtual returns (uint256);

// Update the strategy manager
function setManager(address newManager) external;

// Asset management functions (restricted to manager)
function sendETH(address payable recipient, uint256 amount) external;
function sendToken(address token, address recipient, uint256 amount) external;
function pullToken(address token, address from, uint256 amount) external;
function setAllowance(address token, address spender, uint256 amount) external;

// Get the strategy's token address
function shareToken() external view returns (address);

Additional Functions (ReportedStrategy):

// Get balance from reporter
function balance() external view override returns (uint256);

// Update the reporter contract
function setReporter(address _reporter) external;

Access Control:

  • Most functions require STRATEGY_ADMIN role or manager
  • All asset management functions require manager (STRATEGY_OPERATOR)

Error Conditions:

  • AlreadyInitialized(): Strategy already initialized
  • InvalidAddress(): Zero address for critical parameters
  • InvalidReporter(): Reporter address is zero

tRWA Token

ERC4626-compatible tokenized RWA vault that represents ownership shares in the underlying strategy.

Types of Tokens:

  1. tRWA: Standard implementation with hook system
  2. GatedMintRWA: Two-phase deposit implementation with escrow

Key Functions (tRWA):

// Standard ERC4626 deposit functions (with hook validation)
function deposit(uint256 assets, address receiver) public returns (uint256 shares);
function mint(uint256 shares, address receiver) public returns (uint256 assets);
function withdraw(uint256 assets, address receiver, address owner) public returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner) public returns (uint256 shares);

// Hook management (restricted to strategy)
function addOperationHook(bytes32 operationType, address newHookAddress) external;
function removeOperationHook(bytes32 operationType, address hookAddressToRemove) external;
function reorderOperationHooks(bytes32 operationType, uint256[] calldata newOrderIndices) external;

// Internal deposit/withdraw functions (with hook processing)
function _deposit(address by, address to, uint256 assets, uint256 shares) internal;
function _withdraw(address by, address to, address owner, uint256 assets, uint256 shares) internal;

Additional Functions (GatedMintRWA):

// Two-phase deposit functions
function requestDeposit(uint256 assets, address receiver) external returns (bytes32 depositId);
function mintShares(address receiver, uint256 assets) external returns (uint256 shares);
function batchMintShares(
    bytes32[] calldata depositIds,
    address[] calldata receivers,
    uint256[] calldata assetAmounts,
    uint256 totalAssets
) external returns (uint256 totalShares);

// Escrow management
function escrow() external view returns (address);

Error Conditions:

  • HookCheckFailed(string): Hook rejected the operation
  • WithdrawMoreThanMax(): Attempted to withdraw more than available
  • NotStrategyAdmin(): Caller is not the strategy
  • Various hook validation errors

Events:

  • Deposit(address sender, address receiver, uint256 assets, uint256 shares)
  • Withdraw(address sender, address receiver, address owner, uint256 assets, uint256 shares)
  • WithdrawalQueued(uint256 withdrawalId, address owner, uint256 assets, uint256 shares)
  • HookAdded(bytes32 operationType, address hookAddress, uint256 index)
  • HookRemoved(bytes32 operationType, address hookAddress)
  • HooksReordered(bytes32 operationType, uint256[] indices)

Hooks & Rules Engine

Extensible system for enforcing compliance and custom behavior.

Core Hook Types:

  1. BaseHook: Abstract implementation with standard behavior
  2. KycRulesHook: Implementation for KYC compliance checks
  3. RulesEngine: Manages multiple sub-rules as a single hook

Key Hook Functions:

// Hook validation functions (implement IHook interface)
function onBeforeTransfer(
    address token,
    address from,
    address to,
    uint256 amount
) external view returns (HookOutput memory);

function onBeforeDeposit(
    address token,
    address user,
    uint256 amount,
    address receiver
) external view returns (HookOutput memory);

function onBeforeWithdraw(
    address token,
    address user,
    uint256 amount,
    address receiver,
    address owner
) external view returns (HookOutput memory);

KycRulesHook Specific Functions:

// Add address to allowlist
function allow(address account) external;

// Add address to denylist
function deny(address account) external;

// Remove from both allowlist and denylist
function reset(address account) external;

// Batch versions of above functions
function batchAllow(address[] calldata accounts) external;
function batchDeny(address[] calldata accounts) external;
function batchReset(address[] calldata accounts) external;

// Check if address is allowed
function isAllowed(address account) public view returns (bool);

RulesEngine Specific Functions:

// Add a sub-rule
function addRule(address rule, uint8 priority) external;

// Remove a sub-rule
function removeRule(address rule) external;

// Enable/disable a rule
function setRuleEnabled(address rule, bool enabled) external;

Access Control:

  • KYC functions require KYC_OPERATOR role
  • RulesEngine management requires RULES_ADMIN role

Error Conditions:

  • ZeroAddress(): Address is zero
  • AddressAlreadyDenied(): Trying to allow a denied address
  • AddressNotAllowed(): Address not on allowlist
  • AddressNotDenied(): Address not on denylist
  • InvalidArrayLength(): Empty array in batch operations

Reporters

Oracle system for providing asset valuations.

Types:

  1. BaseReporter: Abstract interface for all reporters
  2. PriceOracleReporter: Implementation for price data

Key Functions:

// Get the latest price/value
function getLatestPrice() external view returns (uint256);

// Report value (abstract, implemented by derived types)
function report() external view returns (bytes memory);

// Update price information (for PriceOracleReporter)
function update(uint256 price) external;

// Set maximum deviation percentage for price updates
function setMaxDeviation(uint16 deviation) external;

Access Control:

  • update: Requires PRICE_UPDATER role
  • setMaxDeviation: Requires PROTOCOL_ADMIN role

Error Conditions:

  • StalePrice(): Price data is too old
  • ExcessiveDeviation(): New price deviates too much from previous
  • UnauthorizedUpdater(): Caller cannot update prices

Multi-Collateral Components

MultiCollateral Registry

Manages multiple Bitcoin-pegged tokens and their conversion rates to SovaBTC.

Key Functions:

// Add a new collateral token
function addCollateral(
    address token,
    uint256 rate,
    uint8 decimals
) external;

// Remove a collateral token  
function removeCollateral(address token) external;

// Update conversion rate for a collateral
function updateRate(address token, uint256 newRate) external;

// Convert collateral amount to SovaBTC value
function convertToSovaBTC(address token, uint256 amount) external view returns (uint256);

// Convert SovaBTC value to collateral amount
function convertFromSovaBTC(address token, uint256 sovaBTCAmount) external view returns (uint256);

// Get all allowed collateral tokens
function getAllCollateralTokens() external view returns (address[] memory);

// Check if token is allowed as collateral
function isAllowedCollateral(address token) external view returns (bool);

Access Control:

  • All management functions require PROTOCOL_ADMIN role

Error Conditions:

  • InvalidCollateral(): Token address is zero or already added
  • InvalidRate(): Rate is zero
  • CollateralNotAllowed(): Token not in allowed list
MultiCollateral Strategy

Manages multiple types of Bitcoin collateral and values them in SovaBTC terms.

Key Functions:

// Deposit collateral tokens (called by vault)
function depositCollateral(address token, uint256 amount) external;

// Deposit redemption funds in SovaBTC (manager only)
function depositRedemptionFunds(uint256 amount) external;

// Get total value of all collateral in SovaBTC terms
function totalCollateralValue() external view returns (uint256);

// Get balance (implements IStrategy interface)
function balance() external view returns (uint256);

State Management:

  • Tracks individual balances for each collateral type
  • Maintains list of held collateral tokens
  • Values all holdings in SovaBTC terms
Enhanced Conduit

Extended to support multi-collateral deposits.

Additional Functions:

// Collect any allowed collateral token
function collectTokens(
    address token,
    address from,
    uint256 amount
) external returns (bool);

Validation:

  • Verifies token is allowed by MultiCollateral Registry
  • Ensures caller is an authorized tRWA contract
  • Routes tokens to the appropriate strategy

Role Manager

Authentication and authorization system with hierarchical roles.

Key Functions:

// Grant role to an address
function grantRole(address user, uint256 role) public;

// Revoke role from an address
function revokeRole(address user, uint256 role) public;

// Configure which role can administer another role
function setRoleAdmin(uint256 targetRole, uint256 adminRole) external;

// Self-renounce a role
function renounceRole(uint256 role) external;

// Check if address has a role
function hasRole(address user, uint256 role) public view returns (bool);

Core Roles (Bit Flags):

uint256 constant PROTOCOL_ADMIN = 1;
uint256 constant STRATEGY_ADMIN = 2;
uint256 constant RULES_ADMIN = 4;
uint256 constant STRATEGY_OPERATOR = 8;
uint256 constant KYC_OPERATOR = 16;
uint256 constant PRICE_UPDATER = 32;

Access Control:

  • grantRole/revokeRole: Caller must be able to manage the role
  • setRoleAdmin: Requires PROTOCOL_ADMIN role

Error Conditions:

  • Unauthorized(): Caller cannot manage the role
  • InvalidRole(): Role bitmask is 0 or invalid

Detailed Protocol Flows

1. Strategy Deployment Flow

# Call Chain:
Registry.deploy(implementation, name, symbol, asset, assetDecimals, manager, initData)
→ Check asset and strategy are authorized
→ Clone strategy using minimal proxy pattern
→ Strategy.initialize(name, symbol, roleManager, manager, asset, assetDecimals, initData)
  → For BasicStrategy:
    → Set strategy state (manager, asset)
    → Deploy new tRWA token with strategy as controller
    → Store token address
  → For ReportedStrategy:
    → Call super.initialize
    → Decode reporter address from initData
    → Set reporter contract
  → For GatedMintRWAStrategy:
    → Override to deploy GatedMintRWA token
    → Deploy GatedMintEscrow for two-phase deposits
→ Register deployed strategy in Registry
→ Return strategy and token addresses

# State Changes:
- Registry: New strategy registered
- New Strategy instance: Initialized with configuration
- New tRWA/GatedMintRWA Token: Deployed and linked to strategy
- For GatedMintRWA: New Escrow contract deployed

# Events:
- Registry: `Deploy(strategy, token, asset)`
- Strategy: `StrategyInitialized(admin, manager, asset, sToken)`
- For ReportedStrategy: `SetReporter(reporter)`

2. Standard Deposit Flow

# Call Chain:
tRWA.deposit(assets, receiver)
→ Calculate shares = (assets * totalSupply) / totalAssets (with virtual shares protection)
→ tRWA._deposit(sender, receiver, assets, shares)
  → Run all deposit hooks (OP_DEPOSIT)
    → For each hook: hook.onBeforeDeposit(token, sender, assets, receiver)
    → If any hook returns approved=false: revert with reason
  → Transfer assets from sender to token via Conduit
    → Conduit.collectDeposit(asset, sender, token, assets)
    → Conduit validates token and calls safeTransferFrom
  → Mint shares to receiver
  → Emit Deposit event

# State Changes:
- tRWA: totalSupply increased by shares amount
- tRWA: receiver's share balance increased
- Asset: transferred from sender to token

# Events:
- tRWA: `Deposit(sender, receiver, assets, shares)`

3. Standard Withdrawal Flow

# Call Chain:
tRWA.withdraw(assets, receiver, owner) or redeem(shares, receiver, owner)
→ Calculate shares or assets based on the call
→ tRWA._withdraw(sender, receiver, owner, assets, shares)
  → Run all withdrawal hooks (OP_WITHDRAW)
    → For each hook: hook.onBeforeWithdraw(token, sender, assets, receiver, owner)
    → If any hook returns queue=true and approved=false:
      → Emit WithdrawalQueued event and return
    → If any hook returns queue=false and approved=false:
      → Revert with reason
  → If sender != owner: Check and spend allowance
  → Verify owner has enough shares
  → Burn shares from owner
  → Transfer assets to receiver
  → Emit Withdraw event

# State Changes:
- tRWA: totalSupply decreased by shares amount
- tRWA: owner's share balance decreased
- Asset: transferred from token to receiver
- If queued: withdrawal information stored in hook

# Events:
- tRWA: `Withdraw(sender, receiver, owner, assets, shares)`
- If queued: `WithdrawalQueued(withdrawalId, owner, assets, shares)`

4. Two-Phase Deposit Flow (GatedMintRWA)

# Deposit Request Phase:
GatedMintRWA.requestDeposit(assets, receiver)
→ Run all deposit hooks (same as standard tRWA)
→ Generate unique depositId
→ Record depositId in tracking arrays
→ Transfer assets to escrow (not directly to token)
  → Conduit.collectDeposit(asset, sender, escrow, assets)
→ Register deposit with escrow:
  → GatedMintEscrow.receiveDeposit(depositId, sender, receiver, assets, expirationTime)
    → Store deposit information
    → Update accounting (totalPendingAssets, userPendingAssets)
→ Emit DepositPending event

# Accept Flow:
GatedMintEscrow.acceptDeposit(depositId) or batchAcceptDeposits([depositIds])
→ Validate deposit exists and is in PENDING state
→ Mark deposit as ACCEPTED
→ Update accounting (totalPendingAssets, userPendingAssets)
→ Transfer assets from escrow to strategy
→ Tell GatedMintRWA to mint shares:
  → GatedMintRWA.mintShares(recipient, assetAmount) or batchMintShares(...)
  → GatedMintRWA calculates and mints shares to recipient
→ Emit DepositAccepted or BatchDepositsAccepted event

# Reject Flow:
GatedMintEscrow.refundDeposit(depositId) or batchRefundDeposits([depositIds])
→ Validate deposit exists and is in PENDING state
→ Mark deposit as REFUNDED
→ Update accounting
→ Return assets to depositor
→ Emit DepositRefunded or BatchDepositsRefunded event

# State Changes:
- Deposit Phase: Assets transferred to escrow, deposit recorded
- Accept Phase: Assets moved to strategy, shares minted to recipient
- Reject Phase: Assets returned to depositor, deposit marked as REFUNDED

# Events:
- Deposit Phase: `DepositPending(depositId, depositor, recipient, assets)`
- Accept Phase: `DepositAccepted(depositId, recipient, assets)` or `BatchDepositsAccepted(depositIds, totalAssets)`
- Reject Phase: `DepositRefunded(depositId, depositor, assets)` or `BatchDepositsRefunded(depositIds, totalAssets)`

5. Multi-Collateral Deposit Flow

# Call Chain:
tRWA.depositWithCollateral(collateralToken, amount, receiver)
→ Run deposit hooks validation
→ Check collateralToken is allowed via MultiCollateral Registry
→ Calculate SovaBTC equivalent value
  → MultiCollateralRegistry.convertToSovaBTC(collateralToken, amount)
  → Handle decimal conversions (8, 18 decimals → 8 decimals)
→ Transfer collateral via enhanced Conduit
  → Conduit.collectTokens(collateralToken, sender, amount)
  → Verify token allowed in registry
  → Transfer to strategy
→ Strategy receives and tracks collateral
  → MultiCollateralStrategy.depositCollateral(collateralToken, amount)
  → Update collateralBalances[token]
  → Track in heldCollateralTokens array
→ Calculate shares based on SovaBTC value
→ Mint shares to receiver

# State Changes:
- Strategy: collateralBalances[token] increased
- Strategy: totalCollateralValue() reflects new deposit
- tRWA: shares minted based on SovaBTC value
- Collateral token: transferred from user to strategy

# Events:
- Strategy: `CollateralDeposited(token, amount)`
- tRWA: `Deposit(sender, receiver, sovaBTCValue, shares)`

6. Multi-Collateral Withdrawal Flow

# Call Chain:
tRWA.withdraw(assets, receiver, owner) or redeem(shares, receiver, owner)
→ Calculate SovaBTC amount to withdraw
→ Run withdrawal hooks validation
→ Strategy returns SovaBTC (not original collateral)
  → Strategy must have sufficient SovaBTC balance
  → Manager responsible for maintaining redemption liquidity
→ Transfer SovaBTC to receiver via standard flow
→ Burn shares from owner

# Important Notes:
- Withdrawals always return SovaBTC, not original collateral
- Strategy manager must deposit SovaBTC redemption funds
- Original collateral remains in strategy for investment

# State Changes:
- tRWA: shares burned
- SovaBTC: transferred from strategy to receiver
- Strategy: SovaBTC balance decreased

# Events:
- tRWA: `Withdraw(sender, receiver, owner, sovaBTCAmount, shares)`

7. KYC/Compliance Validation Flow

# KYC Setup:
KycRulesHook.allow(account) or batchAllow([accounts])
→ Add account to allowlist
→ Emit AddressAllowed event

KycRulesHook.deny(account) or batchDeny([accounts])
→ Add account to denylist, remove from allowlist
→ Emit AddressDenied event

# Validation Flow (for any token operation):
tRWA operation (deposit, withdraw, transfer)
→ Runs appropriate hooks
→ For KYC validation via KycRulesHook:
  → For transfers: KycRulesHook.onBeforeTransfer(token, from, to, amount)
    → Check sender: isAllowed(from)
      → Returns true if explicitly allowed and not denied
      → Returns false if explicitly denied or not explicitly allowed
    → Check receiver: isAllowed(to)
    → If either check fails, return HookOutput(approved=false, reason)
  → For deposits: KycRulesHook.onBeforeDeposit(token, user, amount, receiver)
    → Similar validation of user and receiver
  → For withdrawals: KycRulesHook.onBeforeWithdraw(token, user, amount, receiver, owner)
    → Similar validation plus owner check

# State Changes:
- KYC Setup: Modification of allowlist/denylist
- Validation: No state changes (view functions)

# Events (KYC Setup):
- `AddressAllowed(account, operator)`
- `AddressDenied(account, operator)`
- `AddressRestrictionRemoved(account, operator)`

Integration Guide

Deployment Process

Deploying the Fountfi protocol requires setting up all components in the correct order:

// 1. Deploy Core Infrastructure
RoleManager roleManager = new RoleManager();
Registry registry = new Registry(address(roleManager));

// 2. Grant roles to administrators
// Example: Grant PROTOCOL_ADMIN to deployer
roleManager.grantRole(address(this), 1); // 1 = PROTOCOL_ADMIN

// 3. Deploy and register asset tokens (e.g., USDC)
MockERC20 usdc = new MockERC20("USD Coin", "USDC", 6);
registry.setAsset(address(usdc), true);

// 4. Deploy compliance hooks
KycRulesHook kycHook = new KycRulesHook(address(roleManager));
registry.setHook(address(kycHook), true);

// 5. Deploy and configure reporter (oracle)
PriceOracleReporter reporter = new PriceOracleReporter(
    address(roleManager),
    address(usdc),
    1e6 // Initial price (1.0 with 6 decimals)
);

// 6. Register strategy implementation templates
ReportedStrategy strategyImpl = new ReportedStrategy();
registry.setStrategy(address(strategyImpl), true);

// 7. Deploy a strategy instance via Registry
// Prepare initialization data (including reporter address)
bytes memory initData = abi.encode(address(reporter));

// Deploy the strategy and its token
(address strategy, address token) = registry.deploy(
    address(strategyImpl),
    "Example RWA Token",
    "eRWA",
    address(usdc),
    6, // USDC decimals
    manager, // Strategy manager address
    initData
);

Interacting with the Protocol

For Investors

Multi-Collateral Deposit Flow:

// 1. Choose your collateral token (WBTC, tBTC, cbBTC, or SovaBTC)
address collateralToken = WBTC_ADDRESS; // Example: using WBTC

// 2. Approve the collateral token to the protocol's Conduit
IERC20(collateralToken).approve(conduit, amount);

// 3. Get the vault (tRWA) address
address vaultAddress = 0x2b82b75A0bF1AA01C9474546904bb446FD4E75C7; // From deployment

// 4. Deposit collateral to receive shares
// The vault will automatically convert to SovaBTC value
ItRWA(vaultAddress).depositWithCollateral(collateralToken, amount, address(this));

Standard Deposit Flow (Single Asset):

// 1. Approve asset tokens to the protocol's Conduit
IERC20(assetAddress).approve(registry.conduit(), amount);

// 2. Get strategy's tRWA token
address tokenAddress = IStrategy(strategyAddress).shareToken();

// 3. Deposit assets to receive shares
ItRWA(tokenAddress).deposit(amount, address(this));

Deposit Flow (GatedMintRWA - Two-Phase):

// 1. Approve asset tokens to the protocol's Conduit
IERC20(assetAddress).approve(registry.conduit(), amount);

// 2. Submit deposit request
bytes32 depositId = IGatedMintRWA(tokenAddress).requestDeposit(amount, address(this));

// 3. Wait for manager acceptance
// (Manager calls acceptDeposit on the escrow)

Withdrawal Flow:

// 1. Get the tRWA token address
address tokenAddress = IStrategy(strategyAddress).shareToken();

// 2. Request withdrawal of shares
uint256 shares = 1000e18; // Example amount
ItRWA(tokenAddress).redeem(shares, address(this), address(this));

// 3. If withdrawal is queued, the WithdrawalQueued event will be emitted
// Later, manager must process the queued withdrawal

For Administrators

Multi-Collateral Management:

// Add a new collateral token
MultiCollateralRegistry registry = MultiCollateralRegistry(registryAddress);
registry.addCollateral(
    newTokenAddress,     // Token address
    1e18,               // Rate (1:1 with SovaBTC = 1e18)
    8                   // Token decimals
);

// Update conversion rate for existing collateral
registry.updateRate(tokenAddress, newRate);

// Remove a collateral token
registry.removeCollateral(tokenAddress);

// Deposit redemption funds (as strategy manager)
// Must deposit SovaBTC for user withdrawals
IMultiCollateralStrategy(strategyAddress).depositRedemptionFunds(sovaBTCAmount);

KYC Management:

// Add address to KYC allowlist
KycRulesHook(kycHookAddress).allow(userAddress);

// Add multiple addresses at once
address[] memory users = new address[](2);
users[0] = address1;
users[1] = address2;
KycRulesHook(kycHookAddress).batchAllow(users);

// Remove from KYC allowlist and add to denylist
KycRulesHook(kycHookAddress).deny(userAddress);

// Reset address (clear allowlist and denylist status)
KycRulesHook(kycHookAddress).reset(userAddress);

Hook Management:

// Constants for operation types
bytes32 constant OP_DEPOSIT = keccak256("deposit");
bytes32 constant OP_WITHDRAW = keccak256("withdraw");
bytes32 constant OP_TRANSFER = keccak256("transfer");

// Add a hook to a token
ItRWA(tokenAddress).addOperationHook(OP_DEPOSIT, hookAddress);

// Remove a hook
ItRWA(tokenAddress).removeOperationHook(OP_WITHDRAW, hookAddress);

// Reorder hooks (controls execution priority)
uint256[] memory newOrder = new uint256[](2);
newOrder[0] = 1; // Second hook becomes first
newOrder[1] = 0; // First hook becomes second
ItRWA(tokenAddress).reorderOperationHooks(OP_TRANSFER, newOrder);

Price Oracle Updates:

// Update price information (called by authorized updater)
PriceOracleReporter(reporterAddress).update(newPrice);

// Set maximum allowed deviation for price updates (prevents flash crashes)
// 1000 = 10.00%
PriceOracleReporter(reporterAddress).setMaxDeviation(1000);

Role Management

The protocol implements a role-based access control system with the following key roles:

// Core roles (bit flags)
uint256 constant PROTOCOL_ADMIN = 1;    // Can configure the entire protocol
uint256 constant STRATEGY_ADMIN = 2;    // Can manage strategies and parameters
uint256 constant RULES_ADMIN = 4;       // Can configure compliance rules
uint256 constant STRATEGY_OPERATOR = 8; // Day-to-day operations for strategies
uint256 constant KYC_OPERATOR = 16;     // Manages KYC lists
uint256 constant PRICE_UPDATER = 32;    // Can update price information

Assigning and managing roles:

// Grant STRATEGY_OPERATOR role
roleManager.grantRole(operatorAddress, STRATEGY_OPERATOR);

// Grant multiple roles (bitwise OR)
roleManager.grantRole(adminAddress, PROTOCOL_ADMIN | STRATEGY_ADMIN);

// Check if address has a role
bool hasRole = roleManager.hasRole(checkAddress, STRATEGY_OPERATOR);

// Configure which role can administer another role
roleManager.setRoleAdmin(KYC_OPERATOR, RULES_ADMIN);

// Self-renounce a role
roleManager.renounceRole(STRATEGY_ADMIN);

Advanced Features

Withdrawal Queue Management

For illiquid assets, the protocol supports withdrawal queues through specialized hooks:

// Interface for a hook that implements queued withdrawals
interface IWithdrawQueueHook is IHook {
    // Check if a withdrawal is queued
    function isWithdrawQueued(bytes32 withdrawalId) external view returns (bool);

    // Process a queued withdrawal
    function processQueuedWithdraw(bytes32 withdrawalId) external;

    // Get all pending withdrawals
    function getPendingWithdrawals() external view returns (bytes32[] memory);
}

// Process a specific queued withdrawal (called by STRATEGY_OPERATOR)
WithdrawQueueHook(queueHookAddress).processQueuedWithdraw(withdrawalId);

// Process all pending withdrawals
bytes32[] memory pendingIds = WithdrawQueueHook(queueHookAddress).getPendingWithdrawals();
for (uint i = 0; i < pendingIds.length; i++) {
    WithdrawQueueHook(queueHookAddress).processQueuedWithdraw(pendingIds[i]);
}

Batch Operations for GatedMintRWA

The GatedMintRWA implementation supports batch operations for managing deposits:

// Accept multiple deposits at once
function batchAcceptDeposits(bytes32[] calldata depositIds) external {
    // Internal implementation handles the batch processing
    // More gas efficient than individual calls
}

// Refund multiple deposits at once
function batchRefundDeposits(bytes32[] calldata depositIds) external {
    // Similar batch processing for refunds
}

Security Features

  1. Virtual Shares Protection

ERC4626 tokens are vulnerable to inflation attacks on first deposit. The tRWA token implements protective measures:

// In convertToShares:
if (totalSupply == 0) {
    // First deposit uses fixed rate to prevent inflation attacks
    return _initialSharesPerAsset(assets);
}

// In _initialSharesPerAsset:
function _initialSharesPerAsset(uint256 assets) internal view returns (uint256) {
    // Uses a fixed rate with 36 decimals of precision for the first deposit
    return assets * 1000000 * (10 ** (36 - assetDecimals));
}
  1. Price Deviation Protection

The PriceOracleReporter implements protection against sudden large price swings:

function update(uint256 price) external {
    require(hasRole(msg.sender, PRICE_UPDATER), "UnauthorizedUpdater");

    if (lastPrice > 0 && maxDeviation > 0) {
        // Calculate percentage change with 16-bit precision
        uint256 percentChange = abs(int256(price) - int256(lastPrice)) * 10000 / lastPrice;

        // Revert if change exceeds allowed deviation
        require(percentChange <= maxDeviation, "ExcessiveDeviation");
    }

    lastPrice = price;
    lastUpdateTime = block.timestamp;

    emit PriceUpdated(lastPrice, price);
}

Complete List of Events

Monitoring these events provides full visibility into protocol operations:

Registry Events

event SetStrategy(address indexed implementation, bool allowed);
event SetHook(address indexed implementation, bool allowed);
event SetAsset(address indexed asset, bool allowed);
event Deploy(address indexed strategy, address indexed token, address indexed asset);

RoleManager Events

event RoleGranted(address indexed user, uint256 indexed role, address indexed by);
event RoleRevoked(address indexed user, uint256 indexed role, address indexed by);
event RoleAdminSet(uint256 indexed targetRole, uint256 indexed adminRole, address indexed by);

Strategy Events

event StrategyInitialized(address admin, address manager, address asset, address sToken);
event ManagerChange(address oldManager, address newManager);
event SetReporter(address reporter);

tRWA Token Events

event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares);
event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event WithdrawalQueued(uint256 indexed withdrawalId, address indexed owner, uint256 assets, uint256 shares);
event HookAdded(bytes32 indexed operationType, address indexed hookAddress, uint256 index);
event HookRemoved(bytes32 indexed operationType, address indexed hookAddress);
event HooksReordered(bytes32 indexed operationType, uint256[] indices);

GatedMintRWA and Escrow Events

event DepositPending(bytes32 indexed depositId, address indexed depositor, address indexed recipient, uint256 assets);
event DepositAccepted(bytes32 indexed depositId, address indexed recipient, uint256 assets);
event DepositRefunded(bytes32 indexed depositId, address indexed depositor, uint256 assets);
event BatchDepositsAccepted(bytes32[] depositIds, uint256 totalAssets);
event BatchDepositsRefunded(bytes32[] depositIds, uint256 totalAssets);

KYC Rules Events

event AddressAllowed(address indexed account, address indexed operator);
event AddressDenied(address indexed account, address indexed operator);
event AddressRestrictionRemoved(address indexed account, address indexed operator);
event BatchAddressesAllowed(uint256 count, address indexed operator);
event BatchAddressesDenied(uint256 count, address indexed operator);
event BatchAddressesRestrictionRemoved(uint256 count, address indexed operator);

Reporter Events

event PriceUpdated(uint256 oldPrice, uint256 newPrice);
event MaxDeviationSet(uint16 deviation);

Security Considerations

The protocol implements several security measures to protect against common vulnerabilities:

Role Security

  • Hierarchical role system with fine-grained permissions
  • Proper role separation between admin, operator, and updater roles
  • Role renunciation to prevent privilege lockouts

Oracle Security

  • Price deviation limits to prevent flash crashes
  • Authorized updaters with dedicated role
  • Timestamp tracking to detect stale prices

First Deposit Protection

  • Virtual shares implementation to prevent inflation attacks
  • Fixed initial share rate for the first deposit

Withdrawal Risk Management

  • Queue-based withdrawals for illiquid assets
  • Hook system for enforcing withdrawal restrictions
  • Processing mechanisms for handling queued withdrawals

Two-Phase Deposit Security

  • Escrow to hold funds until approval
  • Expiration timestamps for deposits
  • Refund mechanism for rejected deposits

Hook Ordering

  • Priority-based ordering of hooks
  • Management functions to add, remove, and reorder hooks
  • Critical for controlling validation flow and security

Cloning Pattern Security

  • Proper initialization pattern for cloned implementations
  • Initialization protection to prevent re-initialization attacks
  • Validation of all clone parameters

Development and Testing

Prerequisites

Installation

git clone https://github.com/yourusername/fountfi.git
cd fountfi
forge install

Build

forge build

Test

# Run all tests
forge test

# Run specific test
forge test --match-test test_SpecificFunction

# Run with verbose output
forge test -vvv

# Run gas snapshot
forge snapshot

Deploy

forge script script/SimpleRWADeploy.s.sol:SimpleRWADeployScript --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast

License

MIT License

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published