Skip to content
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