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
- Obtain ETH from the Sepolia Testnet faucets.
- Transform ETH to WETH using the WETH contract.
- Deposit WETH into the LTV Vault.
- Wait for the yield generation period to end.
- Withdraw your WETH or MagicETH from the LTV Vault.
Contract Addresses
Name | Source Code | Address |
---|---|---|
LTV Vault | Source code | 0xe2a7f267124ac3e4131f27b9159c78c521a44f3c |
Magic ETH | Source code | 0x8f7b2044f9aa6fbf495c1cc3bde120df9032ae43 |
Spooky Oracle | Source code | 0x6074d1d4022521147db1fad7bacc486b35a64df3 |
HodlMyBeer Lending Protocol | Source code | 0x1dcd756db287354c4607d5d57621cdfb4456e2d4 |
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 manyshares
correspond toassets
.convertToAssets(uint256 shares) → uint256
— Calculates how manyassets
correspond toshares
.maxDeposit(address receiver) → uint256
— Returns the maximum deposit amount allowed forreceiver
.maxMint(address receiver) → uint256
— Returns the maximum number ofshares
that can be minted forreceiver
.maxWithdraw(address owner) → uint256
— Returns the maximum amount of assets that can be withdrawn byowner
.maxRedeem(address owner) → uint256
— Returns the maximum number ofshares
that can be redeemed byowner
.previewDeposit(uint256 assets) → uint256
— Estimates how manyshares
will be received forassets
.previewMint(uint256 shares) → uint256
— Estimates how manyassets
are needed to mintshares
.previewWithdraw(uint256 assets) → uint256
— Estimates how manyshares
must be burned to withdrawassets
.previewRedeem(uint256 shares) → uint256
— Estimates how manyassets
will be received forshares
.
4626 Write Methods
deposit(uint256 assets, address receiver) → uint256
— Depositsassets
and mintsshares
toreceiver
.mint(uint256 shares, address receiver) → uint256
— Mintsshares
in exchange forassets
.withdraw(uint256 assets, address receiver, address owner) → uint256
— Withdrawsassets
, burningshares
fromowner
.redeem(uint256 shares, address receiver, address owner) → uint256
— Redeemsshares
and transfersassets
toreceiver
.
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 forreceiver
.maxMintCollateral(address receiver) → uint256
— Returns the max number of collateralshares
that can be minted.maxRedeemCollateral(address owner) → uint256
— Returns the max number of collateralshares
that can be redeemed.maxWithdrawCollateral(address owner) → uint256
— Returns the max amount of collateral that can be withdrawn.previewDepositCollateral(uint256 collateralAssets) → uint256
— Estimatesshares
received for collateral deposit.previewMintCollateral(uint256 shares) → uint256
— Estimates collateral assets needed to mintshares
.previewRedeemCollateral(uint256 shares) → uint256
— Estimates assets received for redeemingshares
.previewWithdrawCollateral(uint256 collateralAssets) → uint256
— Estimatesshares
burned for collateral withdrawal.
Collateral Write Methods
depositCollateral(uint256 collateralAssets, address receiver) → uint256
— Deposits collateral and mintsshares
toreceiver
.mintCollateral(uint256 shares, address receiver) → uint256
— Mintsshares
in exchange for collateral assets.withdrawCollateral(uint256 collateralAssets, address receiver, address owner) → uint256
— Withdraws collateral assets, burningshares
fromowner
.redeemCollateral(uint256 shares, address receiver, address owner) → uint256
— Redeemsshares
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 ofowner
.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 thatspender
can use on behalf ofowner
.
ERC-20 Write Methods
approve(address spender, uint256 amount) → bool
— Approvesspender
to useamount
tokens.transfer(address recipient, uint256 amount) → bool
— Transfersamount
tokens torecipient
.transferFrom(address sender, address recipient, uint256 amount) → bool
— Transfersamount
tokens fromsender
torecipient
.
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/givedeltaUserBorrowAssets
amount of borrow assetspreviewExecuteAuctionCollateral(int256 deltaUserCollateralAssets) → int256
- estimates amount of collateral assets user needs to give/receive to receive/givedeltaUserCollateralAssets
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 tonewOwner
.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 links
EIP-4626 Vault Functions
12 Main functions
preview | max | execute | |
---|---|---|---|
deposit | previewDeposit() | maxDeposit() | deposit() |
mint | previewMint() | maxMint() | mint() |
withdraw | previewWithdraw() | maxWithdraw() | withdraw() |
redeem | previewRedeem() | maxRedeem() | redeem() |
All 4626 functions
Category | Function | Description |
---|---|---|
Preview | previewDeposit(uint256) | Simulates shares received for a given asset deposit |
Preview | previewMint(uint256) | Simulates assets required to mint a given amount of shares |
Preview | previewWithdraw(uint256) | Simulates shares needed to withdraw a given amount of assets |
Preview | previewRedeem(uint256) | Simulates assets received for redeeming a given amount of shares |
Max | maxDeposit(address) | Max assets that address can deposit |
Max | maxMint(address) | Max shares that address can mint |
Max | maxWithdraw(address) | Max assets that address can withdraw |
Max | maxRedeem(address) | Max shares that address can redeem |
Execute | deposit(uint256 assets, address receiver) | Deposit assets and receive shares |
Execute | mint(uint256 shares, address receiver) | Mint shares by depositing equivalent assets |
Execute | withdraw(uint256 assets, address receiver, address owner) | Withdraw assets by burning shares |
Execute | redeem(uint256 shares, address receiver, address owner) | Redeem shares for assets |
Other | asset() | Returns the underlying asset address (ERC-20) |
Other | totalAssets() | Total amount of underlying assets managed by the vault |
Other | convertToShares(uint256 assets) | Converts assets to shares |
Other | convertToAssets(uint256 shares) | Converts shares to assets |
ERC-20 | balanceOf(address) | Returns share balance of an account |
ERC-20 | totalSupply() | Total supply of shares |
ERC-20 | transfer(address, uint256) | Transfers shares to another address |
ERC-20 | transferFrom(address, address, uint256) | Transfers shares on behalf of another |
ERC-20 | approve(address, uint256) | Approves a spender |
ERC-20 | allowance(address, address) | Returns remaining allowance for a spender |
ERC-20 | name() | Share token name |
ERC-20 | symbol() | Share token symbol |
ERC-20 | decimals() | 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.