Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ export interface ITokens {
waeroWETHWELL?: string
waeroWETHDEGEN?: string

// Ether.fi
weETH?: string
eETH?: string
KING?: string

// RTokens
eUSD?: string
ETHPLUS?: string
Expand Down Expand Up @@ -340,6 +345,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
maStETH: '0xAdc10669354aAd42A581E6F6cC8990B540AA5689', // our wrapper
RLUSD: '0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD',
aEthRLUSD: '0xFa82580c16A31D0c1bC632A36F82e83EfEF3Eec0',
weETH: '0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee',
eETH: '0x35fA164735182de50811E8e2E824cFb9B6118ac2',
KING: '0x8F08B70456eb22f6109F57b8fafE862ED28E6040',
},
chainlinkFeeds: {
RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02',
Expand Down Expand Up @@ -372,6 +380,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
USDS: '0xfF30586cD0F29eD462364C7e81375FC0C71219b1',
OETHETH: '0x703118C4CbccCBF2AB31913e0f8075fbbb15f563', // OETH/ETH
RLUSD: '0x26C46B7aD0012cA71F2298ada567dC9Af14E7f2A',
weETH: '0x5c9C449BbC9a6075A2c061dF312a35fd1E05fF22', // weETH/ETH
},
AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5',
AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5',
Expand Down
79 changes: 79 additions & 0 deletions contracts/plugins/assets/etherfi/KingAsset.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../../../libraries/Fixed.sol";
import "../Asset.sol";
import "../OracleLib.sol";
import "./vendor/IKing.sol";

/**
* @title KingAsset
* @notice Asset plugin for King token using ETH as intermediate pricing unit
* tok = KING
* UoA = USD
* Pricing: KING/USD = (ETH/KING from fairValueOf) * (USD/ETH from oracle)
*/
contract KingAsset is IAsset, Asset {
using FixLib for uint192;
using OracleLib for AggregatorV3Interface;

/// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0
/// @param ethUsdChainlinkFeed_ {UoA/ref} ETH/USD price feed
/// @param oracleError_ {1} The % the oracle feed can be off by
/// @param erc20_ The King ERC20 token
/// @param maxTradeVolume_ {UoA} The max trade volume, in UoA
/// @param oracleTimeout_ {s} The number of seconds until the oracle becomes invalid
constructor(
uint48 priceTimeout_,
AggregatorV3Interface ethUsdChainlinkFeed_,
uint192 oracleError_,
IERC20Metadata erc20_,
uint192 maxTradeVolume_,
uint48 oracleTimeout_
)
Asset(
priceTimeout_,
ethUsdChainlinkFeed_,
oracleError_,
erc20_,
maxTradeVolume_,
oracleTimeout_
)
{
// Validation is handled by parent Asset contract
}

/// Can revert, used by other contract functions in order to catch errors
/// Should not return FIX_MAX for low
/// Should only return FIX_MAX for high if low is 0
/// Should NOT be manipulable by MEV
/// @return low {UoA/tok} The low price estimate
/// @return high {UoA/tok} The high price estimate
function tryPrice()
external
view
virtual
override
returns (
uint192 low,
uint192 high,
uint192
)
{
// Note: "ref" in this context refers to ETH, used as intermediate pricing unit
// {UoA/ref}
uint192 ethUsdPrice = chainlinkFeed.price(oracleTimeout);

// {ref/tok}
(uint256 ethValue, ) = IKing(address(erc20)).fairValueOf(10**erc20Decimals);
uint192 ethPerKing = _safeWrap(ethValue);

// {UoA/tok} = {UoA/ref} * {ref/tok}
uint192 p = ethUsdPrice.mul(ethPerKing);
uint192 err = p.mul(oracleError, CEIL);
// assert(low <= high); obviously true just by inspection
return (p - err, p + err, 0);
}
}
35 changes: 35 additions & 0 deletions contracts/plugins/assets/etherfi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Ether.fi weETH Collateral Plugin

## Summary

This plugin allows `weETH` holders to use their tokens as collateral in the Reserve Protocol.

As described in the [Ether.fi Documentation](https://etherfi.gitbook.io/etherfi), Ether.fi is a decentralized, non-custodial liquid restaking protocol that consists of two tokens: `eETH` and `weETH`.

Upon depositing ETH into the Ether.fi protocol, users receive `eETH` - a rebasing liquid staking token that earns staking and restaking rewards. The eETH token automatically rebases to reflect accrued rewards. Users can wrap their eETH into `weETH` (wrapped eETH), which is a non-rebasing token suitable for use in DeFi protocols and as collateral.

`weETH` accrues revenue from **staking and restaking rewards** by **increasing** the exchange rate of `eETH` per `weETH`. This exchange rate grows over time as the Ether.fi protocol's validators earn consensus layer rewards and participate in restaking through EigenLayer.

`eETH` contract: <https://etherscan.io/address/0x35fA164735182de50811E8e2E824cFb9B6118ac2>

`weETH` contract: <https://etherscan.io/address/0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee>

### Rewards

Rewards come in the form of KING tokens, which will be distributed via an off-chain procedure and sent to the BackingManager.

KING token: `https://etherscan.io/address/0x8F08B70456eb22f6109F57b8fafE862ED28E6040`

## Implementation

### Units

| tok | ref | target | UoA |
| ----- | ---- | ------ | --- |
| weETH | eETH | ETH | USD |

### Functions

#### refPerTok {ref/tok}

This function returns the rate of `eETH/weETH`, obtained from the [getRate()](https://etherscan.io/address/0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee#readProxyContract) function in the weETH contract.
74 changes: 74 additions & 0 deletions contracts/plugins/assets/etherfi/WeEthCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

import "@openzeppelin/contracts/utils/math/Math.sol";
import "../../../libraries/Fixed.sol";
import "../AppreciatingFiatCollateral.sol";
import "../OracleLib.sol";
import "./vendor/IWeETH.sol";

/**
* @title weETH Collateral
* @notice Collateral plugin for Ether.fi weETH
* tok = weETH
* ref = eETH (pegged to ETH 1:1)
* tar = ETH
* UoA = USD
*/
contract WeEthCollateral is AppreciatingFiatCollateral {
using OracleLib for AggregatorV3Interface;
using FixLib for uint192;

AggregatorV3Interface public immutable targetPerTokChainlinkFeed;
uint48 public immutable targetPerTokChainlinkTimeout;

/// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms
/// @param _targetPerTokChainlinkFeed {target/tok} price of weETH in ETH terms
constructor(
CollateralConfig memory config,
uint192 revenueHiding,
AggregatorV3Interface _targetPerTokChainlinkFeed,
uint48 _targetPerTokChainlinkTimeout
) AppreciatingFiatCollateral(config, revenueHiding) {
require(config.defaultThreshold != 0, "defaultThreshold zero");
require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed");
require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero");

targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed;
targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout;
maxOracleTimeout = uint48(Math.max(maxOracleTimeout, _targetPerTokChainlinkTimeout));
}

/// Can revert, used by other contract functions in order to catch errors
/// @return low {UoA/tok} The low price estimate
/// @return high {UoA/tok} The high price estimate
/// @return pegPrice {target/ref} The actual price observed in the peg
function tryPrice()
external
view
override
returns (
uint192 low,
uint192 high,
uint192 pegPrice
)
{
uint192 targetPerTok = targetPerTokChainlinkFeed.price(targetPerTokChainlinkTimeout);

// {UoA/tok} = {UoA/target} * {target/tok}
uint192 p = chainlinkFeed.price(oracleTimeout).mul(targetPerTok);
uint192 err = p.mul(oracleError, CEIL);

high = p + err;
low = p - err;
// assert(low <= high); obviously true just by inspection

// {target/ref} = {target/tok} / {ref/tok}
pegPrice = targetPerTok.div(underlyingRefPerTok());
}

/// @return {ref/tok} Quantity of whole reference units per whole collateral tokens
function underlyingRefPerTok() public view override returns (uint192) {
return _safeWrap(IWeETH(address(erc20)).getRate());
}
}
16 changes: 16 additions & 0 deletions contracts/plugins/assets/etherfi/vendor/IKing.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

// External interface for King token
interface IKing is IERC20Metadata {
/// @notice Returns the fair value in ETH and USD for an amount of KING tokens
/// @param vaultTokenShares The amount of KING tokens
/// @return ethValue The ETH value of the given KING amount
/// @return usdValue The USD value of the given KING amount
function fairValueOf(uint256 vaultTokenShares)
external
view
returns (uint256 ethValue, uint256 usdValue);
}
13 changes: 13 additions & 0 deletions contracts/plugins/assets/etherfi/vendor/ILiquidityPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

// External interface for Ether.fi's LiquidityPool contract
interface ILiquidityPool {
function amountForShare(uint256 _share) external view returns (uint256);

function sharesForAmount(uint256 _amount) external view returns (uint256);

function getTotalPooledEther() external view returns (uint256);

function rebase(int128 _accruedRewards) external;
}
13 changes: 13 additions & 0 deletions contracts/plugins/assets/etherfi/vendor/IWeETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

// External interface for weETH
interface IWeETH is IERC20Metadata {
function getRate() external view returns (uint256);

function getWeETHByeETH(uint256 _eETHAmount) external view returns (uint256);

function getEETHByWeETH(uint256 _weETHAmount) external view returns (uint256);
}
64 changes: 64 additions & 0 deletions contracts/plugins/mocks/UnpricedKingAssetMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../assets/etherfi/KingAsset.sol";
import "../assets/OracleLib.sol";

// Unpriced KingAsset mock for testing
contract UnpricedKingAssetMock is KingAsset {
using FixLib for uint192;
using OracleLib for AggregatorV3Interface;

bool public unpriced = false;

/// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0
/// @param chainlinkFeed_ Feed units: {UoA/tok}
/// @param oracleError_ {1} The % the oracle feed can be off by
/// @param maxTradeVolume_ {UoA} The max trade volume, in UoA
/// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid
constructor(
uint48 priceTimeout_,
AggregatorV3Interface chainlinkFeed_,
uint192 oracleError_,
IERC20Metadata erc20_,
uint192 maxTradeVolume_,
uint48 oracleTimeout_
)
KingAsset(
priceTimeout_,
chainlinkFeed_,
oracleError_,
erc20_,
maxTradeVolume_,
oracleTimeout_
)
{}

/// tryPrice: mock unpriced by returning (0, FIX_MAX)
function tryPrice()
external
view
override
returns (
uint192 low,
uint192 high,
uint192
)
{
// If unpriced is marked, return 0, FIX_MAX
if (unpriced) return (0, FIX_MAX, 0);

uint192 ethUsdPrice = chainlinkFeed.price(oracleTimeout); // {UoA/ref}
(uint256 ethValue, ) = IKing(address(erc20)).fairValueOf(10**erc20Decimals);
uint192 ethPerKing = _safeWrap(ethValue); // {ref/tok}
uint192 p = ethUsdPrice.mul(ethPerKing); // {UoA/tok}
uint192 delta = p.mul(oracleError, CEIL);
return (p - delta, p + delta, 0);
}

function setUnpriced(bool on) external {
unpriced = on;
}
}
19 changes: 19 additions & 0 deletions contracts/plugins/mocks/WeETHMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.28;

import "./ERC20Mock.sol";

contract WeEthMock is ERC20Mock {
uint256 private _rate;

constructor() ERC20Mock("Mock WeETH", "WeEth") {}

// Mock function for testing
function setRate(uint256 mockRate) external {
_rate = mockRate;
}

function getRate() external view returns (uint256) {
return _rate;
}
}
10 changes: 7 additions & 3 deletions scripts/addresses/1-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"stkAAVE": "0xFDE702794298DB19e2a235782B82aD88053F7335",
"COMP": "0xA32a92073fEB7ed31081656DeFF34518FB5194b9",
"CRV": "0x69841bA9E09019acA0d16Ae9c9724D25d51F6956",
"CVX": "0x2635c3B92c8451F9D1e75BD61FCF87D1eCdf0ad0"
"CVX": "0x2635c3B92c8451F9D1e75BD61FCF87D1eCdf0ad0",
"KING": "0xe64ca4AC2401D6D57cEE942B9ee01494814803f1"
},
"collateral": {
"DAI": "0x8A782e182EeE2299B3DB733659ea764A5a97AdC5",
Expand Down Expand Up @@ -55,7 +56,8 @@
"sUSDS": "0x4FD189996b5344Eb4CF9c749b97C7424D399d24e",
"wOETH": "0xBFAc3e99263B7aE9704eC1c879f7c0a57C6b53e1",
"pyUSD": "0x9A65173df5D5B86E26300Cc9cA5Ff378be6DAeA5",
"saEthRLUSD": "0xb1e61f452CFcF6609C2F4088EC36B4c8dd1806b5"
"saEthRLUSD": "0xb1e61f452CFcF6609C2F4088EC36B4c8dd1806b5",
"weETH": "0x9dc6cEFC09b0917c78a05148d45f6e6594e227de"
},
"erc20s": {
"stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5",
Expand Down Expand Up @@ -111,6 +113,8 @@
"sUSDS": "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD",
"wOETH": "0xDcEe70654261AF21C44c093C300eD3Bb97b78192",
"pyUSD": "0x6c3ea9036406852006290770bedfcaba0e23a0e8",
"saEthRLUSD": "0x4C813CE4e2FF315f0213563A994c20BBF4637444"
"saEthRLUSD": "0x4C813CE4e2FF315f0213563A994c20BBF4637444",
"weETH": "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"KING": "0x8F08B70456eb22f6109F57b8fafE862ED28E6040"
}
}
Loading
Loading