Skip to content

Commit

Permalink
BIP-38: Migrate urBEAN3CRV to urBEANETH (#655)
Browse files Browse the repository at this point in the history
  • Loading branch information
BrendanSanderson authored Oct 21, 2023
2 parents 06e966e + cd9de38 commit 26dd45b
Show file tree
Hide file tree
Showing 54 changed files with 1,633 additions and 747 deletions.
19 changes: 19 additions & 0 deletions protocol/abi/Beanstalk.json
Original file line number Diff line number Diff line change
Expand Up @@ -4420,6 +4420,25 @@
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "totalMigratedBdv",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
Expand Down
4 changes: 2 additions & 2 deletions protocol/contracts/C.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ library C {
//////////////////// Well ////////////////////

uint256 internal constant WELL_MINIMUM_BEAN_BALANCE = 1000_000_000; // 1,000 Beans
address constant internal BEANSTALK_PUMP = 0xBA510f10E3095B83a0F33aa9ad2544E22570a87C;
address constant BEAN_ETH_WELL = 0xBEA0e11282e2bB5893bEcE110cF199501e872bAd;
address internal constant BEANSTALK_PUMP = 0xBA510f10E3095B83a0F33aa9ad2544E22570a87C;
address internal constant BEAN_ETH_WELL = 0xBEA0e11282e2bB5893bEcE110cF199501e872bAd;

function getSeasonPeriod() internal pure returns (uint256) {
return CURRENT_SEASON_PERIOD;
Expand Down
4 changes: 4 additions & 0 deletions protocol/contracts/beanstalk/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ contract Storage {
* @param ownerCandidate Stores a candidate address to transfer ownership to. The owner must claim the ownership transfer.
* @param wellOracleSnapshots A mapping from Well Oracle address to the Well Oracle Snapshot.
* @param beanEthPrice Stores the beanEthPrice during the sunrise() function. Returns 1 otherwise.
* @param migratedBdvs Stores the total migrated BDV since the implementation of the migrated BDV counter. See {LibLegacyTokenSilo.incrementMigratedBdv} for more info.
*/
struct AppStorage {
uint8 deprecated_index;
Expand Down Expand Up @@ -547,4 +548,7 @@ struct AppStorage {
// Well
mapping(address => bytes) wellOracleSnapshots;
uint256 beanEthPrice;

// Silo V3 BDV Migration
mapping(address => uint256) migratedBdvs;
}
98 changes: 67 additions & 31 deletions protocol/contracts/beanstalk/barn/FertilizerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,45 @@
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/SafeCast.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {IFertilizer} from "contracts/interfaces/IFertilizer.sol";
import {AppStorage} from "../AppStorage.sol";
import "contracts/libraries/Token/LibTransfer.sol";
import "contracts/libraries/LibFertilizer.sol";
import "contracts/C.sol";
import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol";
import {LibEthUsdOracle} from "contracts/libraries/Oracle/LibEthUsdOracle.sol";
import {LibFertilizer} from "contracts/libraries/LibFertilizer.sol";
import {LibSafeMath128} from "contracts/libraries/LibSafeMath128.sol";
import {C} from "contracts/C.sol";
import {LibDiamond} from "contracts/libraries/LibDiamond.sol";

/**
* @author Publius
* @title Handles Sprouting Beans from Sprout Tokens
* @title FertilizerFacet handles Minting Fertilizer and Rinsing Sprouts earned from Fertilizer.
**/

contract FertilizerFacet {
using SafeMath for uint256;
using SafeCast for uint256;
using LibSafeMath128 for uint128;

event SetFertilizer(uint128 id, uint128 bpf);

uint256 private constant FERTILIZER_AMOUNT_PRECISION = 1e24;

AppStorage internal s;

struct Supply {
uint128 endBpf;
uint256 supply;
}


/**
* @notice Rinses Rinsable Sprouts earned from Fertilizer.
* @param ids The ids of the Fertilizer to rinse.
* @param mode The balance to transfer Beans to; see {LibTrasfer.To}
*/
function claimFertilized(uint256[] calldata ids, LibTransfer.To mode)
external
payable
Expand All @@ -37,41 +52,49 @@ contract FertilizerFacet {
LibTransfer.sendToken(C.bean(), amount, msg.sender, mode);
}

/**
* @notice Purchase Fertilizer from the Barn Raise with WETH.
* @param wethAmountIn Amount of WETH to buy Fertilizer with 18 decimal precision.
* @param minFertilizerOut The minimum amount of Fertilizer to purchase. Protects against a significant ETH/USD price decrease.
* @param minLPTokensOut The minimum amount of LP tokens to receive after adding liquidity with `weth`.
* @param mode The balance to transfer Beans to; see {LibTrasfer.To}
* @dev The # of Fertilizer minted is equal to the value of the Ether paid in USD.
*/
function mintFertilizer(
uint128 amount,
uint256 minLP,
uint256 wethAmountIn,
uint256 minFertilizerOut,
uint256 minLPTokensOut,
LibTransfer.From mode
) external payable {
uint128 remaining = uint128(LibFertilizer.remainingRecapitalization().div(1e6)); // remaining <= 77_000_000 so downcasting is safe.
if (amount > remaining) amount = remaining;
amount = uint128(LibTransfer.receiveToken(
C.usdc(),
uint256(amount).mul(1e6),
) external payable returns (uint256 fertilizerAmountOut) {
// Transfer the WETH directly to the Well for gas efficiency purposes. The WETH is later synced in {LibFertilizer.addUnderlying}.
wethAmountIn = LibTransfer.transferToken(
IERC20(C.WETH),
msg.sender,
mode
).div(1e6)); // return value <= amount, so downcasting is safe.
uint128 id = LibFertilizer.addFertilizer(
uint128(s.season.current),
amount,
minLP
C.BEAN_ETH_WELL,
uint256(wethAmountIn),
mode,
LibTransfer.To.EXTERNAL
);
C.fertilizer().beanstalkMint(msg.sender, uint256(id), amount, s.bpf);
}

function addFertilizerOwner(
uint128 id,
uint128 amount,
uint256 minLP
) external payable {
LibDiamond.enforceIsContractOwner();
C.usdc().transferFrom(
msg.sender,
address(this),
uint256(amount).mul(1e6)
fertilizerAmountOut = getMintFertilizerOut(wethAmountIn);

require(fertilizerAmountOut >= minFertilizerOut, "Fertilizer: Not enough bought.");
require(fertilizerAmountOut > 0, "Fertilizer: None bought.");

uint128 remaining = uint128(LibFertilizer.remainingRecapitalization().div(1e6)); // remaining <= 77_000_000 so downcasting is safe.
require(fertilizerAmountOut <= remaining, "Fertilizer: Not enough remaining.");

uint128 id = LibFertilizer.addFertilizer(
uint128(s.season.current),
fertilizerAmountOut,
minLPTokensOut
);
LibFertilizer.addFertilizer(id, amount, minLP);
C.fertilizer().beanstalkMint(msg.sender, uint256(id), (fertilizerAmountOut).toUint128(), s.bpf);
}

/**
* @dev Callback from Fertilizer contract in `claimFertilized` function.
*/
function payFertilizer(address account, uint256 amount) external payable {
require(msg.sender == C.fertilizerAddress());
LibTransfer.sendToken(
Expand All @@ -82,6 +105,19 @@ contract FertilizerFacet {
);
}

/**
* @dev Returns the amount of Fertilizer that can be purchased with `wethAmountIn` WETH.
* Can be used to help calculate `minFertilizerOut` in `mintFertilizer`.
* `wethAmountIn` has 18 decimals, `getEthUsdPrice()` has 6 decimals and `fertilizerAmountOut` has 0 decimals.
*/
function getMintFertilizerOut(
uint256 wethAmountIn
) public view returns (uint256 fertilizerAmountOut) {
fertilizerAmountOut = wethAmountIn.mul(
LibEthUsdOracle.getEthUsdPrice()
).div(FERTILIZER_AMOUNT_PRECISION);
}

function totalFertilizedBeans() external view returns (uint256 beans) {
return s.fertilizedIndex;
}
Expand Down
35 changes: 35 additions & 0 deletions protocol/contracts/beanstalk/barn/UnripeFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ contract UnripeFacet is ReentrancyGuard {

event ChangeUnderlying(address indexed token, int256 underlying);

event SwitchUnderlyingToken(address indexed token, address indexed underlyingToken);

event Chop(
address indexed account,
address indexed token,
Expand All @@ -60,6 +62,8 @@ contract UnripeFacet is ReentrancyGuard {

underlyingAmount = _getPenalizedUnderlying(unripeToken, amount, unripeSupply);

require(underlyingAmount > 0, "Chop: no underlying");

LibUnripe.decrementUnderlying(unripeToken, underlyingAmount);

address underlyingToken = s.u[unripeToken].underlyingToken;
Expand Down Expand Up @@ -242,4 +246,35 @@ contract UnripeFacet is ReentrancyGuard {
{
return s.u[unripeToken].underlyingToken;
}

/////////////// UNDERLYING TOKEN MIGRATION //////////////////

/**
* @notice Adds underlying tokens to an Unripe Token.
* @param unripeToken The Unripe Token to add underlying tokens to.
* @param amount The amount of underlying tokens to add.
* @dev Used to migrate the underlying token of an Unripe Token to a new token.
* Only callable by the contract owner.
*/
function addMigratedUnderlying(address unripeToken, uint256 amount) external payable nonReentrant {
LibDiamond.enforceIsContractOwner();
IERC20(s.u[unripeToken].underlyingToken).safeTransferFrom(
msg.sender,
address(this),
amount
);
LibUnripe.incrementUnderlying(unripeToken, amount);
}

/**
* @notice Switches the Underlying Token of an Unripe Token.
* @param unripeToken The Unripe Token to switch the underlying token of.
* @param newUnderlyingToken The new underlying token to switch to.
* @dev `s.u[unripeToken].balanceOfUnderlying` must be 0.
*/
function switchUnderlyingToken(address unripeToken, address newUnderlyingToken) external payable {
LibDiamond.enforceIsContractOwner();
require(s.u[unripeToken].balanceOfUnderlying == 0, "Unripe: Underlying balance > 0");
LibUnripe.switchUnderlyingToken(unripeToken, newUnderlyingToken);
}
}
13 changes: 8 additions & 5 deletions protocol/contracts/beanstalk/init/InitBipBasinIntegration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {LibDiamond} from "contracts/libraries/LibDiamond.sol";

/**
* @author Publius
* @title InitBipWellsIntegration runs the code for the Basin Integration
* @title InitBipBasinIntegration runs the code for the Basin Integration
**/

interface IBDVFacet {
Expand All @@ -28,8 +28,8 @@ contract InitBipBasinIntegration {

AppStorage internal s;

uint32 constant private NEW_BEAN_SEEDS_PER_BDV = 3e6;
uint32 constant private NEW_BEAN_3CRV_SEEDS_PER_BDV = 3.25e6;
uint32 constant private NEW_BEAN_GROWN_STALK_PER_BDV_PER_SEASON = 3e6;
uint32 constant private NEW_BEAN_3CRV_GROWN_STALK_PER_BDV_PER_SEASON = 3.25e6;
uint32 constant private BEAN_ETH_SEEDS_PER_BDV = 4.5e6;

uint32 constant private STALK_ISSUED_PER_BDV = 10000;
Expand All @@ -38,8 +38,11 @@ contract InitBipBasinIntegration {
function init() external {
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();

LibWhitelist.updateStalkPerBdvPerSeasonForToken(C.BEAN, NEW_BEAN_SEEDS_PER_BDV);
LibWhitelist.updateStalkPerBdvPerSeasonForToken(C.CURVE_BEAN_METAPOOL, NEW_BEAN_3CRV_SEEDS_PER_BDV);
LibWhitelist.updateStalkPerBdvPerSeasonForToken(C.BEAN, NEW_BEAN_GROWN_STALK_PER_BDV_PER_SEASON);
LibWhitelist.updateStalkPerBdvPerSeasonForToken(
C.CURVE_BEAN_METAPOOL,
NEW_BEAN_3CRV_GROWN_STALK_PER_BDV_PER_SEASON
);
LibWhitelist.whitelistToken(
C.BEAN_ETH_WELL,
IBDVFacet.wellBdv.selector,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
SPDX-License-Identifier: MIT
*/

pragma solidity =0.7.6;
pragma experimental ABIEncoderV2;

import {AppStorage} from "contracts/beanstalk/AppStorage.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import {C} from "contracts/C.sol";
import {LibDiamond} from "contracts/libraries/LibDiamond.sol";
import {LibUnripe} from "contracts/libraries/LibUnripe.sol";

/**
* Initializes the Migration of the Unripe LP underlying tokens from Bean:3Crv to Bean:Eth.
*/
contract InitMigrateUnripeBean3CrvToBeanEth {
using SafeERC20 for IERC20;

AppStorage internal s;

function init() external {
uint256 balanceOfUnderlying = s.u[C.UNRIPE_LP].balanceOfUnderlying;
IERC20(s.u[C.UNRIPE_LP].underlyingToken).safeTransfer(
LibDiamond.diamondStorage().contractOwner,
balanceOfUnderlying
);
LibUnripe.decrementUnderlying(C.UNRIPE_LP, balanceOfUnderlying);
LibUnripe.switchUnderlyingToken(C.UNRIPE_LP, C.BEAN_ETH_WELL);

// Reset variable to 0 because it wasn't in BIP-36.
delete s.season.withdrawSeasons;
}
}
18 changes: 9 additions & 9 deletions protocol/contracts/beanstalk/metadata/MetadataFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ contract MetadataFacet is MetadataImage {
int96 stemTip = LibTokenSilo.stemTipForToken(token);
require(token != address(0), "Silo: metadata does not exist");
bytes memory attributes = abi.encodePacked(
'\\n\\nToken Symbol: ', getTokenName(token),
'\\nToken Address: ', LibStrings.toHexString(uint256(token), 20),
'\\nId: ', depositId.toHexString(32),
'\\nstem: ', int256(stem).toString(),
'\\ninital stalk per BDV: ', uint256(LibTokenSilo.stalkIssuedPerBdv(token)).toString(),
'\\ngrown stalk per BDV: ', uint256(stemTip - stem).toString(),
'\\nstalk grown per BDV per season: ', uint256(LibTokenSilo.stalkEarnedPerSeason(token)).toString(),
'\\n\\nDISCLAIMER: Due diligence is imperative when assessing this NFT. Opensea and other NFT marketplaces cache the svg output and thus, may require the user to refresh the metadata to properly show the correct values."'
', "attributes": [ { "trait_type": "Token", "value": "', getTokenName(token),
'"}, { "trait_type": "Token Address", "value": "', LibStrings.toHexString(uint256(token), 20),
'"}, { "trait_type": "Id", "value": "', depositId.toHexString(32),
'"}, { "trait_type": "stem", "display_type": "number", "value": ', int256(stem).toString(),
'}, { "trait_type": "inital stalk per BDV", "display_type": "number", "value": ', uint256(LibTokenSilo.stalkIssuedPerBdv(token)).toString(),
'}, { "trait_type": "grown stalk per BDV", "display_type": "number", "value": ', uint256(stemTip - stem).toString(),
'}, { "trait_type": "stalk grown per BDV per season", "display_type": "number", "value": ', uint256(LibTokenSilo.stalkEarnedPerSeason(token)).toString()
);
return string(abi.encodePacked("data:application/json;base64,",LibBytes64.encode(abi.encodePacked(
'{',
'"name": "Beanstalk Silo Deposits", "description": "An ERC1155 representing an asset deposited in the Beanstalk Silo. Silo Deposits gain stalk and bean seignorage.',
'"name": "Beanstalk Silo Deposits", "description": "An ERC1155 representing an asset deposited in the Beanstalk Silo. Silo Deposits gain stalk and bean seignorage. ',
'\\n\\nDISCLAIMER: Due diligence is imperative when assessing this NFT. Opensea and other NFT marketplaces cache the svg output and thus, may require the user to refresh the metadata to properly show the correct values."',
attributes,
string(abi.encodePacked(', "image": "', imageURI(token, stem, stemTip), '"')),
'}'
Expand Down
Loading

0 comments on commit 26dd45b

Please sign in to comment.