LTV protocol

LTV: Curatorless Leveraged Tokenized Vault with a Constant Target Loan-To-Value Ratio

Abstract:

The proposed system is a Curatorless Leveraged Tokenized Vault (LTV) with a Constant Target Loan-To-Value (LTV) ratio. This vault operates without a central curator and allows users to deposit and withdraw funds while receiving tokenized shares representing their holdings. The architec- ture is based on two interconnected EIP4626 vaults. To ensure alignment with the target LTV, an auction-based stimulus system is employed, which incentivizes users to participate in rebalancing actions through rewards or fees. This approach also integrates basic level of MEV protection to guard against frontrunning and maintain system integrity.

Ghost testnet

The LTV Vault is an implementation of an EIP-4626 vault designed for leveraging WETH (Wrapped ETH) deposits to generate yield over time. Users can deposit WETH, hold for a few months, and earn more WETH through a leveraged strategy utilizing the Magic ETH (MAE) token.

The vault borrows WETH and use MagicETH as collateral in the HodlMyBeer Lending protocol and follows the design principles outlined in the research paper: LTV: Curatorless Leveraged Tokenized Vault with a Constant Target Loan-To-Value Ratio

Components

LTV Vault

The LTV Vault is a smart contract that accepts WETH and MagicETH deposits. The vault borrows WETH and keeps MagicETH tokens as collateral in target LTV ratio in HodlMyBeer Lending Protocol. Users can deposit WETH and MagicETH into the vault and receive more WETH and MagicETH after a certain period.

Source code: LTV Vault

Ghost Testnet branch: ghost-testnet

Actual (v0.1.1) tag: ghost_v0.1.1

WETH (Wrapped Testnet ETH)

WETH is the wrapped version of ETH available on the Sepolia Testnet. Users can obtain testnet ETH from various faucets, such as:

User can tranform Sepolia ETH to WETH using the WETH contract

Magic ETH (MAE)

Magic ETH (MAE) is a testnet asset that mimics real-world Liquid Staking Tokens (LSTs) such as Rocket Pool ETH (rETH) or Lido Staked ETH (stETH). MAE accrues value over time, making it an effective asset for yield strategies.

Source code: Magic ETH

Spooky Oracle

The Spooky Oracle provides the real-time redemption price of Magic ETH (MAE) and feeds this data into the LTV Vault. It is maintained by the LTV Protocol Team to ensure accurate pricing for yield calculations and leverage adjustments.

Source code: Spooky Oracle

HodlMyBeer Lending Protocol

The HodlMyBeer Lending Protocol is a dummy lending system that supports an isolated WETH-to-MAE pool. The LTV Vault interacts with this protocol to borrow WETH and provide MagicETH as collateral and execute leveraged yield strategies.

Source code: HodlMyBeer Lending Protocol

How to Use

  1. Obtain ETH from the Sepolia Testnet faucets.
  2. Transform ETH to WETH using the WETH contract.
  3. Deposit WETH into the LTV Vault.
  4. Wait for the yield generation period to end.
  5. Withdraw your WETH or MagicETH from the LTV Vault.

Contract Addresses

Versions

v0.1.1

Actual (v0.1.1) tag: ghost_v0.1.1

Deployed contract implementation: 0x95fe6151ad668984b1c7b193866302b41b90887a

v0.1.0

Previous (v0.1.0) tag: ghost_v0.1.0

Deployed contract implementation: 0xeb81c4fe3d8e3c146a25bf9ea2309977071b6490

Public ABI Description

EIP-4626 (Vault Standard)

4626 Read Methods

  • asset() → address - Returns the vault's underlying asset.
  • totalAssets() → uint256 — Returns the total amount of assets managed by the vault.
  • convertToShares(uint256 assets) → uint256 — Calculates how many shares correspond to assets.
  • convertToAssets(uint256 shares) → uint256 — Calculates how many assets correspond to shares.
  • maxDeposit(address receiver) → uint256 — Returns the maximum deposit amount allowed for receiver.
  • maxMint(address receiver) → uint256 — Returns the maximum number of shares that can be minted for receiver.
  • maxWithdraw(address owner) → uint256 — Returns the maximum amount of assets that can be withdrawn by owner.
  • maxRedeem(address owner) → uint256 — Returns the maximum number of shares that can be redeemed by owner.
  • previewDeposit(uint256 assets) → uint256 — Estimates how many shares will be received for assets.
  • previewMint(uint256 shares) → uint256 — Estimates how many assets are needed to mint shares.
  • previewWithdraw(uint256 assets) → uint256 — Estimates how many shares must be burned to withdraw assets.
  • previewRedeem(uint256 shares) → uint256 — Estimates how many assets will be received for shares.

4626 Write Methods

  • deposit(uint256 assets, address receiver) → uint256 — Deposits assets and mints shares to receiver.
  • mint(uint256 shares, address receiver) → uint256 — Mints shares in exchange for assets.
  • withdraw(uint256 assets, address receiver, address owner) → uint256 — Withdraws assets, burning shares from owner.
  • redeem(uint256 shares, address receiver, address owner) → uint256 — Redeems shares and transfers assets to receiver.

4626 Events

  • Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)
  • Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)

EIP-4626 Collateral (Collateralized Vault Extension)

Collateral Read Methods

  • maxDepositCollateral(address receiver) → uint256 — Returns the max collateral deposit allowed for receiver.
  • maxMintCollateral(address receiver) → uint256 — Returns the max number of collateral shares that can be minted.
  • maxRedeemCollateral(address owner) → uint256 — Returns the max number of collateral shares that can be redeemed.
  • maxWithdrawCollateral(address owner) → uint256 — Returns the max amount of collateral that can be withdrawn.
  • previewDepositCollateral(uint256 collateralAssets) → uint256 — Estimates shares received for collateral deposit.
  • previewMintCollateral(uint256 shares) → uint256 — Estimates collateral assets needed to mint shares.
  • previewRedeemCollateral(uint256 shares) → uint256 — Estimates assets received for redeeming shares.
  • previewWithdrawCollateral(uint256 collateralAssets) → uint256 — Estimates shares burned for collateral withdrawal.

Collateral Write Methods

  • depositCollateral(uint256 collateralAssets, address receiver) → uint256 — Deposits collateral and mints shares to receiver.
  • mintCollateral(uint256 shares, address receiver) → uint256 — Mints shares in exchange for collateral assets.
  • withdrawCollateral(uint256 collateralAssets, address receiver, address owner) → uint256 — Withdraws collateral assets, burning shares from owner.
  • redeemCollateral(uint256 shares, address receiver, address owner) → uint256 — Redeems shares for collateral assets.

Collateral Events

  • DepositCollateral(address indexed sender, address indexed owner, uint256 collateralAssets, uint256 shares)
  • WithdrawCollateral(address indexed sender, address indexed receiver, address indexed owner, uint256 collateralAssets, uint256 shares)

ERC-20 Standard

ERC-20 Read Methods

  • balanceOf(address owner) → uint256 — Returns the token balance of owner.
  • totalSupply() → uint256 — Returns the total supply of tokens.
  • decimals() → uint8 — Returns the number of decimal places for the token.
  • name() → string — Returns the token name.
  • symbol() → string — Returns the token symbol.
  • allowance(address owner, address spender) → uint256 — Returns the remaining number of tokens that spender can use on behalf of owner.

ERC-20 Write Methods

  • approve(address spender, uint256 amount) → bool — Approves spender to use amount tokens.
  • transfer(address recipient, uint256 amount) → bool — Transfers amount tokens to recipient.
  • transferFrom(address sender, address recipient, uint256 amount) → bool — Transfers amount tokens from sender to recipient.

ERC-20 Events

  • Approval(address indexed owner, address indexed spender, uint256 value)
  • Transfer(address indexed from, address indexed to, uint256 value)

Auction System

Auction Read Methods

  • startAuction() → uint256 — Returns the timestamp when the auction started.
  • previewExecuteAuctionBorrow(int256 deltaUserBorrowAssets) → int256 - estimates amount of borrow assets user needs to give/receive to receive/give deltaUserBorrowAssets amount of borrow assets
  • previewExecuteAuctionCollateral(int256 deltaUserCollateralAssets) → int256 - estimates amount of collateral assets user needs to give/receive to receive/give deltaUserCollateralAssets amount of collateral assets.

Auction Write Methods

  • executeAuctionBorrow(int256 deltaUserBorrowAssets) → int256 — Executes an auction for borrow assets.
  • executeAuctionCollateral(int256 deltaUserCollateralAssets) → int256 — Executes an auction for collateral assets.

Auction Events

  • AuctionExecuted(address indexed executor, int256 deltaRealCollateralAssets, int256 deltaRealBorrowAssets)

Low Level Rebalance

Low Level Rebalance Read

  • previewLowLevelBorrow(int256 deltaBorrow) → (int256 deltaCollateral, int256 deltaShares) - Preview low level rebalance function execution with input in borrow assets.

    Input:

    deltaBorrow - amount of assets user wants to withdraw or deposit to the protocol in borrow assets(deltaBorrow < 0 to send borrow assets).

    Output:

    deltaCollateral - amount of assets user will receive or should provide for LTV protocol in collateral assets. If deltaCollateral < 0 - protocol will send -deltaCollateral collateral assets to the user. Otherwise, user will have deltaCollateral assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

  • previewLowLevelCollateral(int256 deltaCollateral) → (int256 deltaBorrow, int256 deltaShares) - Preview low level rebalance function execution with input in collateral assets.

    Input:

    deltaCollateral - amount of assets user wants to withdraw or deposit to the protocol in collateral assets(deltaCollateral > 0 to send collateral assets).

    Output:

    deltaBorrow - amount of assets user will receive or should provide for LTV protocol in borrow assets. If deltaBorrow > 0 - protocol will send deltaBorrow borrow assets to the user. Otherwise, user will have -deltaBorrow assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

  • previewLowLevelShares(int256 deltaShares) → (int256 deltaCollateral, int256 deltaBorrow) - Preview low level rebalance function execution with input in shares.

    Input:

    deltaShares - amount of shares user wants to mint or burn in the protocol (deltaShares > 0 for mint).

    Output:

    deltaCollateral - amount of assets user will receive or should provide for LTV protocol in collateral assets. If deltaCollateral < 0 - protocol will send -deltaCollateral collateral assets to the user. Otherwise, user will have deltaCollateral assets subtracted from his account after execution

    deltaBorrow - amount of assets user will receive or should provide for LTV protocol in borrow assets. If deltaBorrow > 0 - protocol will send deltaBorrow borrow assets to the user. Otherwise, user will have -deltaBorrow assets subtracted from his account after execution

  • previewLowLevelBorrowHint(int256 deltaBorrow, bool isSharesPositiveHint) → (int256 deltaCollateral, int256 deltaShares) - Preview low level rebalance function execution with input in borrow assets with hint to avoid extra gas usage.

    Input:

    deltaBorrow - amount of assets user wants to withdraw or deposit to the protocol in borrow assets(deltaBorrow < 0 to send borrow assets).

    isSharesPositiveHint - should be true if user expects to mint shares. Can save gas spent on calculations.

    Output:

    deltaCollateral - amount of assets user will receive or should provide for LTV protocol in collateral assets. If deltaCollateral < 0 - protocol will send -deltaCollateral collateral assets to the user. Otherwise, user will have deltaCollateral assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

  • previewLowLevelCollateralHint(int256 deltaCollateral, bool isSharesPositiveHint) → (int256 deltaBorrow, int256 deltaShares) - Preview low level rebalance function execution with input in collateral assets with hint to avoid extra gas usage.

    Input:

    deltaCollateral - amount of assets user wants to withdraw or deposit to the protocol in collateral assets(deltaCollateral > 0 to send collateral assets).

    isSharesPositiveHint - should be true if user expects to mint shares. Can save gas spent on calculations.

    Output:

    deltaBorrow - amount of assets user will receive or should provide for LTV protocol in borrow assets. If deltaBorrow > 0 - protocol will send deltaBorrow borrow assets to the user. Otherwise, user will have -deltaBorrow assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

Low Level Rebalance Write

  • executeLowLevelBorrow(int256 deltaBorrow) → (int256 deltaCollateral, int256 deltaShares) - Execute low level rebalance with input in borrow assets.

    Input:

    deltaBorrow - amount of assets user wants to withdraw or deposit to the protocol in borrow assets(deltaBorrow < 0 to send borrow assets).

    Output:

    deltaCollateral - amount of assets user will receive or should provide for LTV protocol in collateral assets. If deltaCollateral < 0 - protocol will send -deltaCollateral collateral assets to the user. Otherwise, user will have deltaCollateral assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

  • executeLowLevelCollateral(int256 deltaCollateral) → (int256 deltaBorrow, int256 deltaShares) - Execute low level rebalance with input in collateral assets.

    Input:

    deltaCollateral - amount of assets user wants to withdraw or deposit to the protocol in collateral assets(deltaCollateral > 0 to send collateral assets).

    Output:

    deltaBorrow - amount of assets user will receive or should provide for LTV protocol in borrow assets. If deltaBorrow > 0 - protocol will send deltaBorrow borrow assets to the user. Otherwise, user will have -deltaBorrow assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

  • executeLowLevelShares(int256 deltaShares) → (int256 deltaCollateral, int256 deltaBorrow) - Execute low level rebalance with input in shares.

    Input:

    deltaShares - amount of shares user wants to mint or burn in the protocol (deltaShares > 0 for mint).

    Output:

    deltaCollateral - amount of assets user will receive or should provide for LTV protocol in collateral assets. If deltaCollateral < 0 - protocol will send -deltaCollateral collateral assets to the user. Otherwise, user will have deltaCollateral assets subtracted from his account after execution

    deltaBorrow - amount of assets user will receive or should provide for LTV protocol in borrow assets. If deltaBorrow > 0 - protocol will send deltaBorrow borrow assets to the user. Otherwise, user will have -deltaBorrow assets subtracted from his account after execution

  • executeLowLevelBorrowHint(int256 deltaBorrow, bool isSharesPositiveHint) → (int256 deltaCollateral, int256 deltaShares) - Execute low level rebalance with input in borrow assets with hint to avoid extra gas usage.

    Input:

    deltaBorrow - amount of assets user wants to withdraw or deposit to the protocol in borrow assets(deltaBorrow < 0 to send borrow assets).

    isSharesPositiveHint - should be true if user expects to mint shares. Can save gas spent on calculations.

    Output:

    deltaCollateral - amount of assets user will receive or should provide for LTV protocol in collateral assets. If deltaCollateral < 0 - protocol will send -deltaCollateral collateral assets to the user. Otherwise, user will have deltaCollateral assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.

  • executeLowLevelCollateralHint(int256 deltaCollateral, bool isSharesPositiveHint) → (int256 deltaBorrow, int256 deltaShares) - Execute low level rebalance with input in collateral assets with hint to avoid extra gas usage.

    Input:

    deltaCollateral - amount of assets user wants to withdraw or deposit to the protocol in collateral assets(deltaCollateral > 0 to send collateral assets).

    isSharesPositiveHint - should be true if user expects to mint shares. Can save gas spent on calculations.

    Output:

    deltaBorrow - amount of assets user will receive or should provide for LTV protocol in borrow assets. If deltaBorrow > 0 - protocol will send deltaBorrow borrow assets to the user. Otherwise, user will have -deltaBorrow assets subtracted from his account after execution

    deltaShares - amount of shares user will receive or burn after execution. If deltaShares > 0 - user will receive deltaShares shares. Otherwise, -deltaShares shares will be burned.


State Representation

State Read Methods

  • borrowToken() → address — Returns the borrow token contract address.
  • collateralToken() → address — Returns the collateral token contract address.
  • futureBorrowAssets() → int256 — Returns current auction borrow assets.
  • futureCollateralAssets() → int256 — Returns current auction collateral assets.
  • futureRewardBorrowAssets() → int256 — Returns current auction reward in borrow assets.
  • futureRewardCollateralAssets() → int256 — Returns current auction reward in collateral assets.
  • getRealBorrowAssets() → uint256 — Returns protocol's current debt in lending protocol in borrow assets.
  • getRealCollateralAssets() → uint256 — Returns protocol's current collateral in lending protocol in collateral assets.
  • maxSafeLTV() → uint128 — Returns the maximum safe loan-to-value ratio.
  • maxTotalAssetsInUnderlying() → uint256 - protocol top border in underlying oracle assets.
  • minProfitLTV() → uint128 — Returns the minimum profitable LTV.
  • targetLTV() → uint128 — Returns the target loan-to-value ratio.
  • oracleConnector() → address - Returns oracle connector address.
  • lendingConnector() → address - Returns lending protocol connector address.
  • feeCollector() → address - Returns fee collector address.

State Events

  • StateUpdated(int256 oldFutureBorrowAssets, int256 oldFutureCollateralAssets, int256 oldFutureRewardBorrowAssets, int256 oldFutureRewardCollateralAssets, uint256 oldStartAuction, int256 newFutureBorrowAssets, int256 newFutureCollateralAssets, int256 newFutureRewardBorrowAssets, int256 newFutureRewardCollateralAssets, uint256 newStartAuction)

Ownership

Ownership Read Methods

  • owner() → address — Returns the address of the current owner.

Ownership Write Methods

  • transferOwnership(address newOwner) — Transfers contract ownership to newOwner.
  • renounceOwnership() — Renounces ownership of the contract.

Administration

Administration Write Methods

  • setTargetLTV(uint128 value) - Set protocol new target LTV.
  • setMaxSafeLTV(uint128 value) - Set protocol new max safe LTV.
  • setMainProfitLTV(uint128 value) - Set protocol new min profit LTV.
  • setFeeCollector(address _feeCollector) - Set protocol new fee collector.
  • setLendingConnector(address _oracleConnector) - Set lending protocol connector address.
  • setMaxGrowthFee(uint256 fee) - Set protocol max growth fee.
  • setMaxTotalAssetsInUnderlying(uint256 _maxTotalAssetsInUnderlying) - Set protocol top border in underlying oracle assets.

Administration Events

  • MaxSafeLTVChanged(uint128 oldValue, uint128 newValue)
  • MinProfitLTVChanged(uint128 oldValue, uint128 newValue)
  • TargetLTVChanged(uint128 oldValue, uint128 newValue)

Ownership Events

  • OwnershipTransferred(address indexed previousOwner, address indexed newOwner)

Connectors

Read Connector Address

  • oracleConnector() → address - Returns oracle connector address.
  • lendingConnector() → address - Returns lending protocol connector address.

EIP-4626 Vault Functions

12 Main functions

previewmaxexecute
depositpreviewDeposit()maxDeposit()deposit()
mintpreviewMint()maxMint()mint()
withdrawpreviewWithdraw()maxWithdraw()withdraw()
redeempreviewRedeem()maxRedeem()redeem()

All 4626 functions

CategoryFunctionDescription
PreviewpreviewDeposit(uint256)Simulates shares received for a given asset deposit
PreviewpreviewMint(uint256)Simulates assets required to mint a given amount of shares
PreviewpreviewWithdraw(uint256)Simulates shares needed to withdraw a given amount of assets
PreviewpreviewRedeem(uint256)Simulates assets received for redeeming a given amount of shares
MaxmaxDeposit(address)Max assets that address can deposit
MaxmaxMint(address)Max shares that address can mint
MaxmaxWithdraw(address)Max assets that address can withdraw
MaxmaxRedeem(address)Max shares that address can redeem
Executedeposit(uint256 assets, address receiver)Deposit assets and receive shares
Executemint(uint256 shares, address receiver)Mint shares by depositing equivalent assets
Executewithdraw(uint256 assets, address receiver, address owner)Withdraw assets by burning shares
Executeredeem(uint256 shares, address receiver, address owner)Redeem shares for assets
Otherasset()Returns the underlying asset address (ERC-20)
OthertotalAssets()Total amount of underlying assets managed by the vault
OtherconvertToShares(uint256 assets)Converts assets to shares
OtherconvertToAssets(uint256 shares)Converts shares to assets
ERC-20balanceOf(address)Returns share balance of an account
ERC-20totalSupply()Total supply of shares
ERC-20transfer(address, uint256)Transfers shares to another address
ERC-20transferFrom(address, address, uint256)Transfers shares on behalf of another
ERC-20approve(address, uint256)Approves a spender
ERC-20allowance(address, address)Returns remaining allowance for a spender
ERC-20name()Share token name
ERC-20symbol()Share token symbol
ERC-20decimals()Share token decimals (commonly 18)

Connectors - Interfaces for Integration.

The LTV protocol uses connectors to stay modular and adaptable, allowing seamless integration with various external systems like lending protocols and price oracles. This design ensures that the vault logic remains unchanged even as the ecosystem evolves or new protocols emerge. By abstracting these components, the protocol can support permissionless deployments across different chains and asset pairs. It also enables governance flexibility.

Lending Protocol Connector

The Lending Protocol Connector determines how the vault interacts with external lending platforms to deposit collateral and borrow assets. It gives the LTV system flexibility to integrate with various protocols such as Aave, Compound, or custom-built solutions like the HodlMyBeer protocol used on testnet. Since the connector acts as a modular layer, the core vault logic remains unchanged even if the underlying lending protocol is swapped out.

Oracle Connector

The Oracle Connector handles how the vault obtains and interprets price data for assets like LSTs or borrow tokens. This data can come from sources like Chainlink, a specialized oracle such as Spooky Oracle, or even DEX-based price feeds. With accurate and modular oracle integration, the vault can continuously evaluate its leverage position and make informed rebalancing decisions.

Slippage Connector

The Slippage Connector in the LTV protocol framework is a mechanism that governs how price slippage is accounted for during operations like rebalancing and auctions. It influences how assets are valued and incentivizes participation in auctions by adjusting for market impact.