Skip to content

Commit

Permalink
scUSDS v2 | Real Yield (#168)
Browse files Browse the repository at this point in the history
* scUSDSv2, adapters & swapper added
  • Loading branch information
dxganta authored Nov 14, 2024
1 parent 275e792 commit 869db44
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 11 deletions.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
optimizer_runs = 1000000
verbosity = 1
solc = "0.8.21"
evm_version = "cancun"

[fuzz]
runs = 256
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/aave-v3/IRewardsController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IRewardsController {
function claimAllRewardsToSelf(address[] calldata assets)
external
returns (address[] memory rewardsList, uint256[] memory claimedAmounts);
}
4 changes: 4 additions & 0 deletions src/lib/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ library Constants {
address public constant AAVE_V3_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
// address of the Aave pool data provider contract
address public constant AAVE_V3_POOL_DATA_PROVIDER = 0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3;
// address of the Aave Rewards Controller contract
address public constant AAVE_V3_REWARDS_CONTROLLER = 0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb;

// address of the Aave v3 "aEthUSDC" token (supply token)
address public constant AAVE_V3_AUSDC_TOKEN = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c;
// address of the Aave v3 "aEthUSDS" token (supply token)
address public constant AAVE_V3_AUSDS_TOKEN = 0x32a6268f9Ba3642Dda7892aDd74f1D34469A4259;
// address of the Aave v3 "aEthUSDT" token (supply token)
address public constant AAVE_V3_AUSDT_TOKEN = 0x23878914EFE38d27C4D67Ab83ed1b93A74D4086a;
// address of the Aave v3 "aEthwstETH" token (supply token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import {AggregatorV3Interface} from "../../interfaces/chainlink/AggregatorV3Inte
import {Constants as C} from "../../lib/Constants.sol";

/**
* @title SDaiWethPriceConverter
* @notice Contract for price conversion between sDAI and WETH.
* @title DaiWethPriceConverter
* @notice Contract for price conversion between DAI/USDS and WETH.
*/
contract SUsdsWethPriceConverter is ISinglePairPriceConverter {
contract DaiWethPriceConverter is ISinglePairPriceConverter {
using FixedPointMathLib for uint256;

/// @notice The address of the asset token (sDAI).
address public constant override asset = C.SUSDS;
address public constant override asset = C.DAI;

/// @notice The address of the target token (WETH).
address public constant override targetToken = C.WETH;
Expand All @@ -24,21 +24,21 @@ contract SUsdsWethPriceConverter is ISinglePairPriceConverter {
AggregatorV3Interface public constant DAI_ETH_PRICE_FEED = AggregatorV3Interface(C.CHAINLINK_DAI_ETH_PRICE_FEED);

/**
* @notice Converts an amount of WETH to the equivalent amount of sDAI.
* @notice Converts an amount of WETH to the equivalent amount of DAI.
* @param _ethAmount The amount of WETH to convert.
* @return The equivalent amount of sDAI.
* @return The equivalent amount of DAI.
*/
function targetTokenToAsset(uint256 _ethAmount) external view override returns (uint256) {
return IERC4626(asset).convertToShares(_ethToDai(_ethAmount));
return _ethToDai(_ethAmount);
}

/**
* @notice Converts an amount of sDAI to the equivalent amount of WETH.
* @param _sDaiAmount The amount of sDAI to convert.
* @notice Converts an amount of DAI to the equivalent amount of WETH.
* @param _daiAmount The amount of DAI to convert.
* @return The equivalent amount of WETH.
*/
function assetToTargetToken(uint256 _sDaiAmount) external view override returns (uint256) {
return _daiToEth(IERC4626(asset).convertToAssets(_sDaiAmount));
function assetToTargetToken(uint256 _daiAmount) external view override returns (uint256) {
return _daiToEth(_daiAmount);
}

/**
Expand Down
45 changes: 45 additions & 0 deletions src/steth/scUSDSv2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";

import {scCrossAssetYieldVault} from "./scCrossAssetYieldVault.sol";
import {Constants as C} from "../lib/Constants.sol";
import {ISinglePairPriceConverter} from "./priceConverter/ISinglePairPriceConverter.sol";
import {ISinglePairSwapper} from "./swapper/ISinglePairSwapper.sol";

/**
* @title scUSDSv2
* @notice Sandclock USDS Vault implementation.
* @dev Inherits from scCrossAssetYieldVault to manage and generate USDS yield.
* @dev There is no USDS Chainlink Feed, but since USDS to DAI is always 1:1 so
* we are using the DAI Price Converter here.
* @dev This vault also receives aUSDS rewards which must be claimed periodically using claimRewards()
*/
contract scUSDSv2 is scCrossAssetYieldVault {
using SafeTransferLib for ERC20;

constructor(
address _admin,
address _keeper,
ERC4626 _targetVault,
ISinglePairPriceConverter _priceConverter,
ISinglePairSwapper _swapper
)
scCrossAssetYieldVault(
_admin,
_keeper,
ERC20(C.USDS),
_targetVault,
_priceConverter,
_swapper,
"Sandclock USDS Real Yield Vault",
"scUSDSv2"
)
{
ERC20(C.DAI).safeApprove(C.DAI_USDS_CONVERTER, type(uint256).max);
ERC20(C.USDS).safeApprove(C.DAI_USDS_CONVERTER, type(uint256).max);
}
}
94 changes: 94 additions & 0 deletions src/steth/scUsds-adapters/AaveV3ScUsdsAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {WETH} from "solmate/tokens/WETH.sol";
import {IRewardsController} from "../../interfaces/aave-v3/IRewardsController.sol";

import {Constants as C} from "../../lib/Constants.sol";
import {IPool} from "aave-v3/interfaces/IPool.sol";
import {IPoolDataProvider} from "aave-v3/interfaces/IPoolDataProvider.sol";
import {IAdapter} from "../IAdapter.sol";

/**
* @title Aave v3 Lending Protocol Adapter
* @notice Facilitates lending and borrowing for the Aave v3 lending protocol
*/
contract AaveV3ScUsdsAdapter is IAdapter {
using SafeTransferLib for ERC20;
using SafeTransferLib for WETH;

ERC20 public constant usds = ERC20(C.USDS);
WETH public constant weth = WETH(payable(C.WETH));

// Aave v3 pool contract
IPool public constant pool = IPool(C.AAVE_V3_POOL);
// Aave v3 pool data provider contract
IPoolDataProvider public constant aaveV3PoolDataProvider = IPoolDataProvider(C.AAVE_V3_POOL_DATA_PROVIDER);
// Aave v3 "aEthUSDS" token (supply token)
ERC20 public constant aUsds = ERC20(C.AAVE_V3_AUSDS_TOKEN);
// Aave v3 "variableDebtEthWETH" token (variable debt token)
ERC20 public constant dWeth = ERC20(C.AAVE_V3_VAR_DEBT_WETH_TOKEN);

/// @inheritdoc IAdapter
uint256 public constant override id = 1;

/// @inheritdoc IAdapter
function setApprovals() external override {
usds.safeApprove(address(pool), type(uint256).max);
weth.safeApprove(address(pool), type(uint256).max);
}

/// @inheritdoc IAdapter
function revokeApprovals() external override {
usds.safeApprove(address(pool), 0);
weth.safeApprove(address(pool), 0);
}

/// @inheritdoc IAdapter
function supply(uint256 _amount) external override {
pool.supply(address(usds), _amount, address(this), 0);
}

/// @inheritdoc IAdapter
function borrow(uint256 _amount) external override {
pool.borrow(address(weth), _amount, C.AAVE_VAR_INTEREST_RATE_MODE, 0, address(this));
}

/// @inheritdoc IAdapter
function repay(uint256 _amount) external override {
pool.repay(address(weth), _amount, C.AAVE_VAR_INTEREST_RATE_MODE, address(this));
}

/// @inheritdoc IAdapter
function withdraw(uint256 _amount) external override {
pool.withdraw(address(usds), _amount, address(this));
}

/// @inheritdoc IAdapter
function claimRewards(bytes calldata) external override {
address[] memory assets = new address[](1);
assets[0] = C.AAVE_V3_AUSDS_TOKEN;

IRewardsController(C.AAVE_V3_REWARDS_CONTROLLER).claimAllRewardsToSelf(assets);
}

/// @inheritdoc IAdapter
function getCollateral(address _account) external view override returns (uint256) {
return aUsds.balanceOf(_account);
}

/// @inheritdoc IAdapter
function getDebt(address _account) external view override returns (uint256) {
return dWeth.balanceOf(_account);
}

/// @inheritdoc IAdapter
function getMaxLtv() external view override returns (uint256) {
(, uint256 ltv,,,,,,,,) = aaveV3PoolDataProvider.getReserveConfigurationData(address(usds));

// ltv is returned as a percentage with 2 decimals (e.g. 80% = 8000) so we need to multiply by 1e14
return ltv * 1e14;
}
}
63 changes: 63 additions & 0 deletions src/steth/swapper/UsdsWethSwapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";

import {Constants as C} from "../../lib/Constants.sol";
import {ISinglePairSwapper} from "../swapper/ISinglePairSwapper.sol";
import {SwapperLib} from "./SwapperLib.sol";
import {UniversalSwapper} from "./UniversalSwapper.sol";
import {IDaiUsds} from "../../interfaces/sky/IDaiUsds.sol";

contract UsdsWethSwapper is ISinglePairSwapper, UniversalSwapper {
/// @notice The address of the asset token (USDS).
address public constant override asset = address(C.USDS);

/// @notice The address of the target token (WETH).
address public constant override targetToken = address(C.WETH);

/// @notice DAI token used as an intermediate token for swaps.
ERC20 public constant dai = ERC20(C.DAI);

/// @notice The Dai - USDS converter contract from sky
IDaiUsds public constant converter = IDaiUsds(C.DAI_USDS_CONVERTER);

/// @notice Encoded swap path from WETH to DAI.
bytes public constant swapPath = abi.encodePacked(targetToken, uint24(500), C.USDC, uint24(100), dai);

/**
* @notice Swap WETH for USDS.
* @param _wethAmount The amount of WETH to swap.
* @param _usdsAmountOutMin The minimum amount of USDS to receive.
* @return usdsReceived The amount of USDS received from the swap.
*/
function swapTargetTokenForAsset(uint256 _wethAmount, uint256 _usdsAmountOutMin)
external
override
returns (uint256 usdsReceived)
{
// swap weth to dai
usdsReceived = SwapperLib._uniswapSwapExactInputMultihop(targetToken, _wethAmount, _usdsAmountOutMin, swapPath);

// swap dai to usds
converter.daiToUsds(address(this), usdsReceived);
}

/**
* @notice Swap USDS for an exact amount of WETH.
* @param _wethAmountOut The exact amount of WETH desired.
* @return usdsSpent The amount of USDS spent to receive `_wethAmountOut` of WETH.
*/
function swapAssetForExactTargetToken(uint256 _wethAmountOut) external override returns (uint256 usdsSpent) {
// convert all USDS to DAI
uint256 usdsBalance = ERC20(asset).balanceOf(address(this));
converter.usdsToDai(address(this), usdsBalance);

// Swap DAI for exact amount of WETH
usdsSpent = SwapperLib._uniswapSwapExactOutputMultihop(address(dai), _wethAmountOut, usdsBalance, swapPath);

// convert remaining DAI back to USDS
converter.daiToUsds(address(this), usdsBalance - usdsSpent);
}
}
Loading

0 comments on commit 869db44

Please sign in to comment.