Skip to content

Commit

Permalink
Add LTV tracker (#2)
Browse files Browse the repository at this point in the history
* Add VaultUserLtvTracker

* Add snapshots

* Review fixes

* Add deploy script

* Add snapshots using forge test --isolate

* Fix test, fix gitignore

* Rename deploy script

* Check new user equals to prev user
  • Loading branch information
evgeny-stakewise authored Oct 22, 2024
1 parent 15c6232 commit 2d0ae9e
Show file tree
Hide file tree
Showing 5 changed files with 574 additions and 4 deletions.
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
broadcast

# Docs
docs/

# Dotenv file
.env
*.env

# IDE
.idea
Expand Down
37 changes: 37 additions & 0 deletions script/DeployVaultUserLtvTracker.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import {Script} from 'forge-std/Script.sol';
import {console} from 'forge-std/console.sol';
import {VaultUserLtvTracker} from '../src/VaultUserLtvTracker.sol';

contract DeployVaultUserLtvTracker is Script {
struct ConfigParams {
address keeper;
address osTokenVaultController;
}

function _readEnvVariables() internal view returns (ConfigParams memory params) {
params.keeper = vm.envAddress('KEEPER');
params.osTokenVaultController = vm.envAddress('OS_TOKEN_VAULT_CONTROLLER');
}

function run() external {
vm.startBroadcast(vm.envUint('PRIVATE_KEY'));

console.log('Deploying from: ', msg.sender);

// Read environment variables.
ConfigParams memory params = _readEnvVariables();

// Deploy VaultUserLtvTracker.
VaultUserLtvTracker tracker = new VaultUserLtvTracker(params.keeper, params.osTokenVaultController);
console.log('VaultUserLtvTracker deployed at: ', address(tracker));

vm.stopBroadcast();
}

// excludes this contract from coverage report
function test() public {}
}
113 changes: 113 additions & 0 deletions src/VaultUserLtvTracker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: AGPL-3.0-only

pragma solidity ^0.8.26;

import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
import {IKeeperRewards} from '@stakewise-core/interfaces/IKeeperRewards.sol';
import {IVaultState} from '@stakewise-core/interfaces/IVaultState.sol';
import {IVaultOsToken} from '@stakewise-core/interfaces/IVaultOsToken.sol';
import {IOsTokenVaultController} from '@stakewise-core/interfaces/IOsTokenVaultController.sol';
import {IVaultUserLtvTracker} from './interfaces/IVaultUserLtvTracker.sol';

/**
* @title VaultUserLtvTracker
* @author StakeWise
* @notice Stores user with a maximum LTV value for each vault
*/
contract VaultUserLtvTracker is IVaultUserLtvTracker {
uint256 private constant _wad = 1e18;
IKeeperRewards private immutable _keeperRewards;
IOsTokenVaultController private immutable _osTokenVaultController;

/**
* @dev Constructor
* @param keeper The address of the Keeper contract
* @param osTokenVaultController The address of the OsTokenVaultController contract
*/
constructor(address keeper, address osTokenVaultController) {
_keeperRewards = IKeeperRewards(keeper);
_osTokenVaultController = IOsTokenVaultController(osTokenVaultController);
}

// Mapping to store the user with the highest LTV for each vault
mapping(address vault => address user) public vaultToUser;

/// @inheritdoc IVaultUserLtvTracker
function updateVaultMaxLtvUser(
address vault,
address newUser,
IKeeperRewards.HarvestParams calldata harvestParams
) external {
// Get the previous max LTV user for the vault
address prevUser = vaultToUser[vault];

if (newUser == prevUser) {
return;
}

// Calculate the LTV for both users
uint256 newLtv = _calculateLtv(vault, newUser, harvestParams);
uint256 prevLtv = _calculateLtv(vault, prevUser, harvestParams);

// If the new user has a higher LTV, update the record
if (newLtv > prevLtv) {
vaultToUser[vault] = newUser;
}
}

/// @inheritdoc IVaultUserLtvTracker
function getVaultMaxLtv(
address vault,
IKeeperRewards.HarvestParams calldata harvestParams
) external returns (uint256) {
address user = vaultToUser[vault];

// Calculate the latest LTV for the stored user
return _calculateLtv(vault, user, harvestParams);
}

/**
* @dev Internal function for calculating LTV
* @param vault The address of the vault
* @param user The address of the user
* @param harvestParams The harvest params to use for updating the vault state
*/
function _calculateLtv(
address vault,
address user,
IKeeperRewards.HarvestParams calldata harvestParams
) private returns (uint256) {
// Skip calculation for zero address
if (user == address(0)) {
return 0;
}

// Update vault state to get up-to-date value of user stake
if (_keeperRewards.canHarvest(vault)) {
IVaultState(vault).updateState(harvestParams);
}

// Get OsToken position
uint256 osTokenShares = IVaultOsToken(vault).osTokenPositions(user);

// Convert OsToken position to Wei
uint256 osTokenAssets = _osTokenVaultController.convertToAssets(osTokenShares);

if (osTokenAssets == 0) {
return 0;
}

// Get user stake in a vault
uint256 vaultShares = IVaultState(vault).getShares(user);

// Convert user stake to Wei
uint256 vaultAssets = IVaultState(vault).convertToAssets(vaultShares);

if (vaultAssets == 0) {
return 0;
}

// Calculate Loan-To-Value ratio
return Math.mulDiv(osTokenAssets, _wad, vaultAssets);
}
}
35 changes: 35 additions & 0 deletions src/interfaces/IVaultUserLtvTracker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: AGPL-3.0-only

pragma solidity ^0.8.26;

import {IKeeperRewards} from '@stakewise-core/interfaces/IKeeperRewards.sol';

/**
* @title IVaultUserLtvTracker
* @author StakeWise
* @notice Defines the interface for the VaultUserLtvTracker contract
*/
interface IVaultUserLtvTracker {
/**
* @notice Updates the vault's max LTV user
* @param vault The address of the vault
* @param user The address of the user
* @param harvestParams The harvest params to use for updating the vault state
*/
function updateVaultMaxLtvUser(
address vault,
address user,
IKeeperRewards.HarvestParams calldata harvestParams
) external;

/**
* @notice Gets the current highest LTV for the vault
* @param vault The address of the vault
* @param harvestParams The harvest params to use for updating the vault state
* @return The current highest LTV for the vault
*/
function getVaultMaxLtv(
address vault,
IKeeperRewards.HarvestParams calldata harvestParams
) external returns (uint256);
}
Loading

0 comments on commit 2d0ae9e

Please sign in to comment.