diff --git a/protocol/abi/Beanstalk.json b/protocol/abi/Beanstalk.json
index 12943063d8..91ac3ff6e5 100644
--- a/protocol/abi/Beanstalk.json
+++ b/protocol/abi/Beanstalk.json
@@ -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": [
diff --git a/protocol/contracts/C.sol b/protocol/contracts/C.sol
index 569df18015..cdfcec97e7 100644
--- a/protocol/contracts/C.sol
+++ b/protocol/contracts/C.sol
@@ -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;
diff --git a/protocol/contracts/beanstalk/AppStorage.sol b/protocol/contracts/beanstalk/AppStorage.sol
index b3d3f33698..a6a54ffe7b 100644
--- a/protocol/contracts/beanstalk/AppStorage.sol
+++ b/protocol/contracts/beanstalk/AppStorage.sol
@@ -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;
@@ -547,4 +548,7 @@ struct AppStorage {
// Well
mapping(address => bytes) wellOracleSnapshots;
uint256 beanEthPrice;
+
+ // Silo V3 BDV Migration
+ mapping(address => uint256) migratedBdvs;
}
\ No newline at end of file
diff --git a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol
index a6b8b36de0..75c02783f9 100644
--- a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol
+++ b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol
@@ -5,23 +5,32 @@
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 {
@@ -29,6 +38,12 @@ contract FertilizerFacet {
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
@@ -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(
@@ -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;
}
diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol
index 6cd0b3f345..63f79ddb10 100644
--- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol
+++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol
@@ -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,
@@ -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;
@@ -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);
+ }
}
diff --git a/protocol/contracts/beanstalk/init/InitBipBasinIntegration.sol b/protocol/contracts/beanstalk/init/InitBipBasinIntegration.sol
index b03ab87918..f24252b389 100644
--- a/protocol/contracts/beanstalk/init/InitBipBasinIntegration.sol
+++ b/protocol/contracts/beanstalk/init/InitBipBasinIntegration.sol
@@ -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 {
@@ -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;
@@ -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,
diff --git a/protocol/contracts/beanstalk/init/InitMigrateUnripeBean3CrvToBeanEth.sol b/protocol/contracts/beanstalk/init/InitMigrateUnripeBean3CrvToBeanEth.sol
new file mode 100644
index 0000000000..a3e0743dfa
--- /dev/null
+++ b/protocol/contracts/beanstalk/init/InitMigrateUnripeBean3CrvToBeanEth.sol
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/protocol/contracts/beanstalk/metadata/MetadataFacet.sol b/protocol/contracts/beanstalk/metadata/MetadataFacet.sol
index 16290a63ba..b4df7233c1 100644
--- a/protocol/contracts/beanstalk/metadata/MetadataFacet.sol
+++ b/protocol/contracts/beanstalk/metadata/MetadataFacet.sol
@@ -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), '"')),
'}'
diff --git a/protocol/contracts/beanstalk/metadata/MetadataImage.sol b/protocol/contracts/beanstalk/metadata/MetadataImage.sol
index 0977878f5b..b22edf6a2a 100644
--- a/protocol/contracts/beanstalk/metadata/MetadataImage.sol
+++ b/protocol/contracts/beanstalk/metadata/MetadataImage.sol
@@ -36,7 +36,7 @@ contract MetadataImage {
);
}
- function generateImage(address token, int96 stem, int96 stemTip) internal view returns (string memory) {
+ function generateImage(address token, int96 stem, int96 stemTip) internal pure returns (string memory) {
int96 grownStalkPerBdv = stemTip - stem;
return string(
abi.encodePacked(
@@ -71,8 +71,8 @@ contract MetadataImage {
beanToken(),
bean3CRVToken(),
urBeanToken(),
- urBean3CRVToken(),
beanETHCP2WellToken(),
+ urBeanETHCP2WellToken(),
fullLeafRow(),
''
));
@@ -409,36 +409,22 @@ contract MetadataImage {
return beanTemplateToken(false);
}
- function bean3CRVToken() internal pure returns (string memory) {
- return beanLPTemplateToken(false);
- }
-
function urBeanToken() internal pure returns (string memory) {
return beanTemplateToken(true);
}
- function urBean3CRVToken() internal pure returns (string memory) {
- return beanLPTemplateToken(true);
+ function beanETHCP2WellToken() internal pure returns (string memory) {
+ return beanETHCP2WellTemplateToken(false);
}
- function beanTemplateToken(bool ripe) internal pure returns (string memory) {
- return string(abi.encodePacked(
- '',
- ''
- )
- );
+ function urBeanETHCP2WellToken() internal pure returns (string memory) {
+ return beanETHCP2WellTemplateToken(true);
}
- function beanLPTemplateToken(bool ripe) internal pure returns (string memory) {
+ function bean3CRVToken() internal pure returns (string memory) {
return string(abi.encodePacked(
- '',
'',
'',
@@ -448,21 +434,33 @@ contract MetadataImage {
''
)
);
- }
+ }
- function beanETHCP2WellToken() internal pure returns (string memory) {
+ function beanTemplateToken(bool ripe) internal pure returns (string memory) {
+ return string(abi.encodePacked(
+ '',
+ ''
+ )
+ );
+ }
+
+ function beanETHCP2WellTemplateToken(bool ripe) internal pure returns (string memory) {
return string(abi.encodePacked(
- '',
- '',
- '',
- '',
+ '',
'',
''
)
);
}
-
function useAssetTransform(string memory assetName, int256 x, int256 y) internal pure returns (string memory) {
return string(abi.encodePacked(
'