Skip to content

Commit

Permalink
bip-16 (#66) Whitelist LUSD
Browse files Browse the repository at this point in the history
  • Loading branch information
publiuss committed Apr 11, 2022
1 parent 8bf0734 commit f5e69b7
Show file tree
Hide file tree
Showing 24 changed files with 849 additions and 92 deletions.
58 changes: 58 additions & 0 deletions bips/bip-16.md
@@ -0,0 +1,58 @@
# BIP-16: Whitelist BEAN:LUSD Curve Pool

- [Proposer](#proposer)
- [Summary](#summary)
- [Proposal](#proposal)
- [Economic Rationale](#economic-rationale)
- [Technical Rationale](#technical-rationale)
- [Effective](#effective)

## Proposer:

Beanstalk Farms

## Summary:

Add the BEAN:LUSD Curve pool to the Silo whitelist for 1 Stalk and 3 Seeds per flash-loan-resistant Bean denominated value (BDV) Deposited.

## Proposal:

Add LP tokens for the BEAN:LUSD Curve pool (X) to the Silo whitelist.

**Token address:** 0xD652c40fBb3f06d6B58Cb9aa9CFF063eE63d465D

**BDV function:** The BDV of BEAN:LUSD LP tokens is calculated from the virtual price of X, the LUSD price in 3CRV derived from the LUSD:3CRV pool (lusd3CrvPrice), and the BEAN price in 3CRV derived from the BEAN:3CRV pool (bean3CrvPrice).

Both lusd3CrvPrice and bean3CrvPrice are calculated using the getY() function in the curve metapool contract using the reserves in the pools in the last block ($\Xi - 1$).

We propose the BDV function for X is:
$$
BDV(x) = x * \text{virtual_price}(X) * \text{min}(1, \text{lusd3CrvPrice} / \text{bean3CrvPrice})
$$
**Stalk per BDV:** 1 Stalk per BDV.

**Seeds per BDV:** 3 Seeds per BDV.

## Economic Rationale:

Adding the BEAN:LUSD Curve pool to the Silo whitelist is beneficial to the success of both Beanstalk and Liquity. While the Silo’s yield should attract initial capital to the pool, the Stalk and Seed system incentivizes long-term liquidity that helps to further stabilize the prices of both BEAN and LUSD.

Over $300M in LUSD is currently deposited in Liquity's Stability Pool, earning ~6.3% APR from LQTY early adopter rewards at this time. The emission of these LQTY rewards follows a yearly halving schedule, and the Liquity Stability Pool holds more LUSD than is necessary to cover liquidations.

If BIP-16 is passed, the BEAN:LUSD pool’s inclusion in the Silo will offer LUSD holders the opportunity to directly participate in Beanstalk's governance and yield opportunities, providing additional utility to LUSD.

The pool is likely to attract capital from both BEAN holders and LUSD holders. The Silo’s Stalk and Seed system will reward long-term liquidity and should increase the stickiness of the capital in the pool. The pool also helps to decrease BEAN price deviations from peg and diversifies BEAN liquidity, increasing its correlation with a stable asset and reducing the correlation of its price with a more volatile asset like Ether.

The BEAN:LUSD Curve pool was launched on March 24, 2022, and currently holds over $500,000 in BEAN and LUSD. There is no capital requirement for a pool to be added to the Silo whitelist—the pool will be whitelisted upon the passage of this BIP.

## Technical Rationale:

By using the virtual price and the reserves in the last block, the BDV function is flash-loan-resistant.

## Effective:

Effective immediately upon commit.

## Reward:

5,000 Beans to Beanstalk Farms.
40 changes: 40 additions & 0 deletions protocol/contracts/C.sol
Expand Up @@ -5,6 +5,9 @@
pragma solidity =0.7.6;
pragma experimental ABIEncoderV2;

import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "./interfaces/IBean.sol";
import "./interfaces/ICurveMetapool.sol";
import "./libraries/Decimal.sol";

/**
Expand Down Expand Up @@ -62,6 +65,12 @@ library C {
uint256 private constant MAX_SOIL_DENOMINATOR = 4; // 25%
uint256 private constant COMPLEX_WEATHER_DENOMINATOR = 1000; // 0.1%

// Contracts
address private constant BEAN = 0xDC59ac4FeFa32293A95889Dc396682858d52e5Db;
address private constant UNISWAP_V2_BEAN_ETH = 0x87898263B6C5BABe34b4ec53F22d98430b91e371;
address private constant CURVE_BEAN_METAPOOL = 0x3a70DfA7d2262988064A2D051dd47521E43c9BdD;
address private constant CURVE_LUSD_METAPOOL = 0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA;
address private constant CURVE_BEAN_LUSD_POOL = 0xD652c40fBb3f06d6B58Cb9aa9CFF063eE63d465D;

/**
* Getters
Expand Down Expand Up @@ -175,4 +184,35 @@ library C {
return ROOTS_BASE;
}

function beanAddress() internal pure returns (address) {
return BEAN;
}

function uniswapV2PairAddress() internal pure returns (address) {
return UNISWAP_V2_BEAN_ETH;
}

function curveMetapoolAddress() internal pure returns (address) {
return CURVE_BEAN_METAPOOL;
}

function curveLUSDMetapoolAddress() internal pure returns (address) {
return CURVE_LUSD_METAPOOL;
}

function curveBeanLUSDAddress() internal pure returns (address) {
return CURVE_BEAN_LUSD_POOL;
}

function bean() internal pure returns (IBean) {
return IBean(BEAN);
}

function uniswapV2Pair() internal pure returns (IUniswapV2Pair) {
return IUniswapV2Pair(UNISWAP_V2_BEAN_ETH);
}

function curveMetapool() internal pure returns (ICurveMetapool) {
return ICurveMetapool(CURVE_BEAN_METAPOOL);
}
}
36 changes: 36 additions & 0 deletions protocol/contracts/farm/facets/BDVFacet.sol
@@ -0,0 +1,36 @@
/*
* SPDX-License-Identifier: MIT
*/

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import "../../C.sol";
import "../../libraries/Curve/LibBeanMetaCurve.sol";
import "../../libraries/Curve/LibBeanLUSDCurve.sol";
import "../../libraries/LibBeanEthUniswap.sol";

/*
* @author Publius
* @title BDVFacet holds the Curve MetaPool BDV function.
*/
contract BDVFacet {

using SafeMath for uint256;

function curveToBDV(uint256 amount) external view returns (uint256) {
return LibBeanMetaCurve.bdv(amount);
}

function lusdToBDV(uint256 amount) external view returns (uint256) {
return LibBeanLUSDCurve.bdv(amount);
}

function bdv(address token, uint256 amount) external view returns (uint256) {
if (token == C.beanAddress()) return amount.mul(1);
else if (token == C.uniswapV2PairAddress()) return LibBeanEthUniswap.lpToLPBeans(amount);
else if (token == C.curveMetapoolAddress()) return LibBeanMetaCurve.bdv(amount);
else if (token == C.curveBeanLUSDAddress()) return LibBeanLUSDCurve.bdv(amount);
revert("BDV: Token not whitelisted");
}
}
5 changes: 3 additions & 2 deletions protocol/contracts/farm/facets/ConvertFacet/ConvertSilo.sol
Expand Up @@ -13,6 +13,7 @@ import "../../../libraries/Silo/LibLPSilo.sol";
import "../../../libraries/LibCheck.sol";
import "../../../libraries/LibMarket.sol";
import "../../../C.sol";
import "../../../libraries/LibBeanEthUniswap.sol";

/**
* @author Publius
Expand Down Expand Up @@ -61,13 +62,13 @@ contract ConvertSilo is ReentrancyGuard {
LibMarket.allocateBeans(transferAmount);
}

w.i = w.stalkRemoved.div(LibLPSilo.lpToLPBeans(lp.add(w.newLP)), "Silo: No LP Beans.");
w.i = w.stalkRemoved.div(LibBeanEthUniswap.lpToLPBeans(lp.add(w.newLP)), "Silo: No LP Beans.");
uint32 depositSeason = season().sub(uint32(w.i.div(C.getSeedsPerLPBean())));

if (lp > 0) pair().transferFrom(msg.sender, address(this), lp);

lp = lp.add(w.newLP);
_depositLP(lp, LibLPSilo.lpToLPBeans(lp), depositSeason);
_depositLP(lp, LibBeanEthUniswap.lpToLPBeans(lp), depositSeason);
LibSilo.updateBalanceOfRainStalk(msg.sender);
LibMarket.refund();
LibCheck.balanceCheck();
Expand Down
18 changes: 0 additions & 18 deletions protocol/contracts/farm/facets/CurveBDVFacet.sol

This file was deleted.

3 changes: 2 additions & 1 deletion protocol/contracts/farm/facets/SiloFacet/LPSilo.sol
Expand Up @@ -7,6 +7,7 @@ pragma experimental ABIEncoderV2;

import "./UpdateSilo.sol";
import "../../../libraries/Silo/LibLPSilo.sol";
import "../../../libraries/LibBeanEthUniswap.sol";

/**
* @author Publius
Expand Down Expand Up @@ -46,7 +47,7 @@ contract LPSilo is UpdateSilo {
**/

function _depositLP(uint256 amount) internal {
uint256 lpb = LibLPSilo.lpToLPBeans(amount);
uint256 lpb = LibBeanEthUniswap.lpToLPBeans(amount);
require(lpb > 0, "Silo: No Beans under LP.");
LibLPSilo.incrementDepositedLP(amount);
uint256 seeds = lpb.mul(C.getSeedsPerLPBean());
Expand Down
33 changes: 33 additions & 0 deletions protocol/contracts/farm/init/InitBip16.sol
@@ -0,0 +1,33 @@
/*
SPDX-License-Identifier: MIT
*/

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import {IBean} from "../../interfaces/IBean.sol";

/**
* @author Publius
* @title InitBip16 initializes BIP-16: It whitelists the Bean:LUSD Curve Plain Pool into the Silo and pays the publius address 5,000 Beans.
**/

interface IBS {
function whitelistToken(address token, bytes4 selector, uint32 stalk, uint32 seeds) external;
function lusdToBDV(uint256 amount) external view returns (uint256);
}

contract InitBip16 {
address private constant BEAN_ADDRESS = address(0xDC59ac4FeFa32293A95889Dc396682858d52e5Db);
address private constant PUBLIUS_ADDRESS = address(0x925753106FCdB6D2f30C3db295328a0A1c5fD1D1);
uint256 private constant PAYMENT = 5000000000;

address private constant BEAN_LUSD_ADDRESS = address(0xD652c40fBb3f06d6B58Cb9aa9CFF063eE63d465D);
uint32 private constant STALK = 10000;
uint32 private constant SEEDS = 3;

function init() external {
IBS(address(this)).whitelistToken(BEAN_LUSD_ADDRESS, IBS.lusdToBDV.selector, STALK, SEEDS);
IBean(BEAN_ADDRESS).mint(PUBLIUS_ADDRESS, PAYMENT);
}
}
18 changes: 18 additions & 0 deletions protocol/contracts/interfaces/ICurveMetapool.sol
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma experimental ABIEncoderV2;
pragma solidity ^0.7.6;

interface ICurveMetapool {
function A_precise() external view returns (uint256);
function get_balances() external view returns (uint256[2] memory);
function totalSupply() external view returns (uint256);
function add_liquidity(uint256[] memory amounts, uint256 min_mint_amount) external returns (uint256);
function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external returns (uint256);
function balances(int128 i) external view returns (uint256);
function fee() external view returns (uint256);
function coins(uint256 i) external view returns (address);
function get_virtual_price() external view returns (uint256);
function calc_token_amount(uint256[] calldata amounts, bool deposit) external view returns (uint256);
function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256);
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;
}
45 changes: 45 additions & 0 deletions protocol/contracts/libraries/Curve/LibBeanLUSDCurve.sol
@@ -0,0 +1,45 @@
/**
* SPDX-License-Identifier: MIT
**/

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import "./LibMetaCurve.sol";

interface IPlainCurve {
function get_virtual_price() external view returns (uint256);
}

library LibBeanLUSDCurve {
using SafeMath for uint256;

uint256 private constant N_COINS = 2;
uint256 private constant i = 0;
uint256 private constant j = 1;

address private constant BEAN_METAPOOL = 0x3a70DfA7d2262988064A2D051dd47521E43c9BdD;
uint256 private constant BEAN_DECIMALS = 6;
uint256 private constant BEAN_RM = 1e12;
uint256 private constant I_BEAN_RM = 1e6;

address private constant POOL = 0xD652c40fBb3f06d6B58Cb9aa9CFF063eE63d465D;
address private constant TOKEN_METAPOOL = 0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA;
uint256 private constant TOKEN_DECIMALS = 18;

function bdv(uint256 amount) internal view returns (uint256) {
uint256 rate = getRate();
uint256 price = IPlainCurve(POOL).get_virtual_price();
if (rate < 1e18) price = price.mul(rate).div(1e18);
return amount.mul(price).div(1e30);
}

function getRate() internal view returns (uint256 rate) {
uint256 bean3CrvPrice = LibMetaCurve.price(BEAN_METAPOOL, BEAN_DECIMALS);
uint256 token3CrvPrice = LibMetaCurve.price(TOKEN_METAPOOL, TOKEN_DECIMALS);
rate = token3CrvPrice.mul(I_BEAN_RM).div(
bean3CrvPrice
);
}
}
36 changes: 36 additions & 0 deletions protocol/contracts/libraries/Curve/LibBeanMetaCurve.sol
@@ -0,0 +1,36 @@
/**
* SPDX-License-Identifier: MIT
**/

pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import "./LibMetaCurve.sol";

library LibBeanMetaCurve {
using SafeMath for uint256;

uint256 private constant A_PRECISION = 100;
address private constant POOL = address(0x3a70DfA7d2262988064A2D051dd47521E43c9BdD);
address private constant CRV3_POOL = address(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);
uint256 private constant N_COINS = 2;
uint256 private constant RATE_MULTIPLIER = 1e12; // Bean has 6 Decimals
uint256 private constant PRECISION = 1e18;
uint256 private constant i = 0;
uint256 private constant j = 1;

function bdv(uint256 amount) internal view returns (uint256) {
// By using previous balances and the virtual price, we protect against flash loan
uint256[2] memory balances = IMeta3Curve(POOL).get_previous_balances();
uint256 virtualPrice = IMeta3Curve(POOL).get_virtual_price();
uint256[2] memory xp = LibMetaCurve.getXP(balances, RATE_MULTIPLIER);
uint256 a = IMeta3Curve(POOL).A_precise();
uint256 D = LibCurve.getD(xp, a);
uint256 price = LibCurve.getPrice(xp, a, D, RATE_MULTIPLIER);
uint256 totalSupply = D * PRECISION / virtualPrice;
uint256 beanValue = balances[0].mul(amount).div(totalSupply);
uint256 curveValue = xp[1].mul(amount).div(totalSupply).div(price);
return beanValue.add(curveValue);
}
}

0 comments on commit f5e69b7

Please sign in to comment.