diff --git a/.gitignore b/.gitignore index bb6931eb09..e70a30a799 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ node_modules .DS_Store # Local Netlify folder .netlify + +protocol/lib/forge-std diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/Silo.sol b/protocol/contracts/beanstalk/silo/SiloFacet/Silo.sol index d5f8855319..c690a06027 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/Silo.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/Silo.sol @@ -5,9 +5,18 @@ pragma solidity =0.7.6; pragma abicoder v2; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "./SiloExit.sol"; - +import {AppStorage, Storage} from "contracts/beanstalk/AppStorage.sol"; +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import {ReentrancyGuard} from "contracts/beanstalk/ReentrancyGuard.sol"; +import {LibSafeMath128} from "contracts/libraries/LibSafeMath128.sol"; +import {LibSafeMath32} from "contracts/libraries/LibSafeMath32.sol"; +import {LibGerminate} from "contracts/libraries/Silo/LibGerminate.sol"; +import {LibTokenSilo} from "contracts/libraries/Silo/LibTokenSilo.sol"; +import {LibSilo} from "contracts/libraries/Silo/LibSilo.sol"; +import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/SafeCast.sol"; +import {LibBytes} from "contracts/libraries/LibBytes.sol"; +import {C} from "contracts/C.sol"; /** * @title Silo * @author Publius, Pizzaman1337, Brean @@ -21,7 +30,7 @@ import "./SiloExit.sol"; * "Season of Plenty". */ -contract Silo is SiloExit { +contract Silo is ReentrancyGuard { using SafeMath for uint256; using SafeERC20 for IERC20; using LibSafeMath128 for uint128; @@ -106,7 +115,7 @@ contract Silo is SiloExit { uint256 accountStalk = s.a[account].s.stalk; // Calculate balance of Earned Beans. - beans = _balanceOfEarnedBeans(account, accountStalk); + beans = LibSilo._balanceOfEarnedBeans(account, accountStalk); stemTip = LibTokenSilo.stemTipForToken(C.BEAN); if (beans == 0) return (0, stemTip); diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol b/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol index ba78b43520..4047808279 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol @@ -305,12 +305,4 @@ contract SiloFacet is TokenSilo { _claimPlenty(msg.sender); } - function bdv(address token, uint256 amount) - external - view - returns (uint256 _bdv) - { - _bdv = LibTokenSilo.beanDenominatedValue(token, amount); - } - } diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/SiloExit.sol b/protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol similarity index 66% rename from protocol/contracts/beanstalk/silo/SiloFacet/SiloExit.sol rename to protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol index db896210b6..2e45945efa 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/SiloExit.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol @@ -1,34 +1,26 @@ -/* /** * SPDX-License-Identifier: MIT **/ pragma solidity =0.7.6; -pragma abicoder v2; - -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "contracts/beanstalk/ReentrancyGuard.sol"; -import "contracts/libraries/Silo/LibSilo.sol"; -import "contracts/libraries/Silo/LibTokenSilo.sol"; -import "contracts/libraries/Silo/LibLegacyTokenSilo.sol"; -import "contracts/libraries/LibSafeMath32.sol"; -import "contracts/libraries/LibSafeMath128.sol"; -import "contracts/C.sol"; +pragma experimental ABIEncoderV2; + +import {AppStorage, Storage, Account} from "contracts/beanstalk/AppStorage.sol"; +import {LibLegacyTokenSilo} from "contracts/libraries/Silo/LibLegacyTokenSilo.sol"; +import {ReentrancyGuard} from "contracts/beanstalk/ReentrancyGuard.sol"; +import {LibTokenSilo} from "contracts/libraries/Silo/LibTokenSilo.sol"; +import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; +import {LibBytes} from "contracts/libraries/LibBytes.sol"; +import {LibSilo} from "contracts/libraries/Silo/LibSilo.sol"; +import {C} from "contracts/C.sol"; /** - * @title SiloExit - * @author Publius, Brean, Pizzaman1337 - * @notice Exposes public view functions for Silo total balances, account - * balances, account update history, and Season of Plenty (SOP) balances. - * - * Provides utility functions like {_season} for upstream usage throughout - * SiloFacet. - */ -contract SiloExit is ReentrancyGuard { + * @author Brean + * @title SiloGettersFacet contains view functions related to the silo. + **/ +contract SiloGettersFacet is ReentrancyGuard { + using SafeMath for uint256; - using LibSafeMath32 for uint32; - using LibSafeMath128 for uint128; /** * @dev Stores account-level Season of Plenty balances. @@ -50,12 +42,102 @@ contract SiloExit is ReentrancyGuard { uint256 plenty; } + //////////////////////// GETTERS //////////////////////// + + /** + * @notice Find the amount and BDV of `token` that `account` has Deposited in stem index `stem`. + * + * Returns a deposit tuple `(uint256 amount, uint256 bdv)`. + * + * @return amount The number of tokens contained in this Deposit. + * @return bdv The BDV associated with this Deposit. + */ + function getDeposit( + address account, + address token, + int96 stem + ) external view returns (uint256, uint256) { + return LibTokenSilo.getDeposit(account, token, stem); + } + + /** + * @notice Get the total amount of `token` currently Deposited in the Silo across all users. + */ + function getTotalDeposited(address token) external view returns (uint256) { + return s.siloBalances[token].deposited; + } + + /** + * @notice Get the total bdv of `token` currently Deposited in the Silo across all users. + */ + function getTotalDepositedBdv(address token) external view returns (uint256) { + return s.siloBalances[token].depositedBdv; + } + + /** + * @notice Get the Storage.SiloSettings for a whitelisted Silo token. + * + * Contains: + * - the BDV function selector + * - Stalk per BDV + * - stalkEarnedPerSeason + * - milestoneSeason + * - lastStem + */ + function tokenSettings(address token) external view returns (Storage.SiloSettings memory) { + return s.ss[token]; + } + + //////////////////////// ERC1155 //////////////////////// + + /** + * @notice returns the amount of tokens in a Deposit. + * + * @dev see {getDeposit} for both the bdv and amount. + */ + function balanceOf(address account, uint256 depositId) external view returns (uint256 amount) { + return s.a[account].deposits[depositId].amount; + } + + /** + * @notice returns an array of amounts corresponding to Deposits. + */ + function balanceOfBatch( + address[] calldata accounts, + uint256[] calldata depositIds + ) external view returns (uint256[] memory) { + require(accounts.length == depositIds.length, "ERC1155: ids and amounts length mismatch"); + uint256[] memory balances = new uint256[](accounts.length); + for (uint256 i = 0; i < accounts.length; i++) { + balances[i] = s.a[accounts[i]].deposits[depositIds[i]].amount; + } + return balances; + } + + /** + * @notice outputs the depositID given an token address and stem. + */ + function getDepositId(address token, int96 stem) external pure returns (uint256) { + return LibBytes.packAddressAndStem(token, stem); + } + + /** + * @notice returns the bean denominated value ("bdv") of a token amount. + */ + function bdv(address token, uint256 amount) + external + view + returns (uint256 _bdv) + { + _bdv = LibTokenSilo.beanDenominatedValue(token, amount); + } + //////////////////////// UTILTIES //////////////////////// /** * @notice Get the last Season in which `account` updated their Silo. */ - function lastUpdate(address account) public view returns (uint32) { + function lastUpdate(address account) external view returns (uint32) { return s.a[account].lastUpdate; } @@ -64,14 +146,14 @@ contract SiloExit is ReentrancyGuard { /** * @notice Returns the total supply of Stalk. Does NOT include Grown Stalk. */ - function totalStalk() public view returns (uint256) { + function totalStalk() external view returns (uint256) { return s.s.stalk; } /** * @notice Returns the total supply of Roots. */ - function totalRoots() public view returns (uint256) { + function totalRoots() external view returns (uint256) { return s.s.roots; } @@ -81,7 +163,7 @@ contract SiloExit is ReentrancyGuard { * supply. Earned Beans are simply seignorage Beans held by Beanstalk for * distribution to Stalkholders during {SiloFacet-plant}. */ - function totalEarnedBeans() public view returns (uint256) { + function totalEarnedBeans() external view returns (uint256) { return s.earnedBeans; } @@ -94,7 +176,7 @@ contract SiloExit is ReentrancyGuard { * @dev Earned Stalk earns Bean Mints, but Grown Stalk does not due to * computational complexity. */ - function balanceOfStalk(address account) public view returns (uint256) { + function balanceOfStalk(address account) external view returns (uint256) { return s.a[account].s.stalk.add(balanceOfEarnedStalk(account)); } @@ -112,7 +194,7 @@ contract SiloExit is ReentrancyGuard { * When a Flood occurs, Plenty is distributed based on a Farmer's balance * of Roots when it started Raining. */ - function balanceOfRoots(address account) public view returns (uint256) { + function balanceOfRoots(address account) external view returns (uint256) { return s.a[account].roots; } @@ -124,7 +206,7 @@ contract SiloExit is ReentrancyGuard { * @dev This passes in the last stem the user mowed at and the current stem */ function balanceOfGrownStalk(address account, address token) - public + external view returns (uint256) { @@ -148,7 +230,7 @@ contract SiloExit is ReentrancyGuard { address token, int96 stem ) - public + external view returns (uint grownStalk) { @@ -164,41 +246,7 @@ contract SiloExit is ReentrancyGuard { view returns (uint256 beans) { - beans = _balanceOfEarnedBeans(account, s.a[account].s.stalk); - } - - /** - * @dev Internal function to compute `account` balance of Earned Beans. - * - * The number of Earned Beans is equal to the difference between: - * - the "expected" Stalk balance, determined from the account balance of - * Roots. - * - the "account" Stalk balance, stored in account storage. - * divided by the number of Stalk per Bean. - * The earned beans from the latest season - */ - function _balanceOfEarnedBeans(address account, uint256 accountStalk) - internal - view - returns (uint256 beans) { - // There will be no Roots before the first Deposit is made. - if (s.s.roots == 0) return 0; - - uint256 stalk = s.s.stalk - .mul(s.a[account].roots) - .div(s.s.roots); - - // Beanstalk rounds down when minting Roots. Thus, it is possible that - // balanceOfRoots / totalRoots * totalStalk < s.a[account].s.stalk. - // As `account` Earned Balance balance should never be negative, - // Beanstalk returns 0 instead. - if (stalk <= accountStalk) return 0; - - // Calculate Earned Stalk and convert to Earned Beans. - beans = (stalk - accountStalk).div(C.STALK_PER_BEAN); // Note: SafeMath is redundant here. - if (beans > s.earnedBeans) return s.earnedBeans; - - return beans; + beans = LibSilo._balanceOfEarnedBeans(account, s.a[account].s.stalk); } /** @@ -256,7 +304,7 @@ contract SiloExit is ReentrancyGuard { * @notice Returns the last Season that it started Raining resulting in a * Season of Plenty. */ - function lastSeasonOfPlenty() public view returns (uint32) { + function lastSeasonOfPlenty() external view returns (uint32) { return s.season.lastSop; } @@ -265,7 +313,7 @@ contract SiloExit is ReentrancyGuard { * Seasons of Plenty. */ function balanceOfPlenty(address account) - public + external view returns (uint256 plenty) { @@ -276,7 +324,7 @@ contract SiloExit is ReentrancyGuard { * @notice Returns the `account` balance of Roots the last time it was * Raining during a Silo update. */ - function balanceOfRainRoots(address account) public view returns (uint256) { + function balanceOfRainRoots(address account) external view returns (uint256) { return s.a[account].sop.roots; } @@ -292,11 +340,10 @@ contract SiloExit is ReentrancyGuard { sop.lastRain = s.a[account].lastRain; sop.lastSop = s.a[account].lastSop; sop.roots = s.a[account].sop.roots; - sop.plenty = balanceOfPlenty(account); + sop.plenty = LibSilo.balanceOfPlenty(account); sop.plentyPerRoot = s.a[account].sop.plentyPerRoot; } - //////////////////////// STEM //////////////////////// /** @@ -311,7 +358,7 @@ contract SiloExit is ReentrancyGuard { * with a larger seeds per BDV, to a lower seeds per BDV. */ function stemTipForToken(address token) - public + external view returns (int96 _stemTip) { @@ -325,7 +372,7 @@ contract SiloExit is ReentrancyGuard { * kept for legacy reasons. */ function seasonToStem(address token, uint32 season) - public + external view returns (int96 stem) { @@ -346,14 +393,14 @@ contract SiloExit is ReentrancyGuard { /** * @notice returns the season in which beanstalk initalized siloV3. */ - function stemStartSeason() public view virtual returns (uint16) { + function stemStartSeason() external view virtual returns (uint16) { return s.season.stemStartSeason; } /** * @notice returns whether an account needs to migrate to siloV3. */ - function migrationNeeded(address account) public view returns (bool) { + function migrationNeeded(address account) external view returns (bool) { return LibSilo.migrationNeeded(account); } @@ -365,4 +412,5 @@ contract SiloExit is ReentrancyGuard { function _season() internal view returns (uint32) { return s.season.current; } + } diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol b/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol index dbcb9c4317..b7a12f8f76 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol @@ -13,9 +13,9 @@ import "./Silo.sol"; * @notice This contract contains functions for depositing, withdrawing and * claiming whitelisted Silo tokens. * - * - "Removing a Deposit" only removes from the `account`; the total amount - * deposited in the Silo is decremented during withdrawal, _after_ a Withdrawal - * is created. See "Finish Removal". + * "Removing a Deposit" only removes from the `account`; the total amount + * deposited in the Silo is decremented during withdrawal, _after_ a Withdrawal + * is created. See "Finish Removal". */ contract TokenSilo is Silo { using SafeMath for uint256; @@ -28,7 +28,7 @@ contract TokenSilo is Silo { * There is no "AddDeposits" event because there is currently no operation in which Beanstalk * creates multiple Deposits in different stems: * - * - `deposit()` always places the user's deposit in the current `_season()`. + * - `deposit()` always places the user's deposit in the current season. * - `convert()` collapses multiple deposits into a single Season to prevent loss of Stalk. * * @param account The account that added a Deposit. @@ -419,82 +419,4 @@ contract TokenSilo is Silo { return bdvs; } - //////////////////////// GETTERS //////////////////////// - - /** - * @notice Find the amount and BDV of `token` that `account` has Deposited in stem index `stem`. - * - * Returns a deposit tuple `(uint256 amount, uint256 bdv)`. - * - * @return amount The number of tokens contained in this Deposit. - * @return bdv The BDV associated with this Deposit. - */ - function getDeposit( - address account, - address token, - int96 stem - ) external view returns (uint256, uint256) { - return LibTokenSilo.getDeposit(account, token, stem); - } - - /** - * @notice Get the total amount of `token` currently Deposited in the Silo across all users. - */ - function getTotalDeposited(address token) external view returns (uint256) { - return s.siloBalances[token].deposited; - } - - /** - * @notice Get the total bdv of `token` currently Deposited in the Silo across all users. - */ - function getTotalDepositedBdv(address token) external view returns (uint256) { - return s.siloBalances[token].depositedBdv; - } - - /** - * @notice Get the Storage.SiloSettings for a whitelisted Silo token. - * - * Contains: - * - the BDV function selector - * - Stalk per BDV - * - stalkEarnedPerSeason - * - milestoneSeason - * - lastStem - */ - function tokenSettings(address token) external view returns (Storage.SiloSettings memory) { - return s.ss[token]; - } - - //////////////////////// ERC1155 //////////////////////// - - /** - * @notice returns the amount of tokens in a Deposit. - * - * @dev see {getDeposit} for both the bdv and amount. - */ - function balanceOf(address account, uint256 depositId) external view returns (uint256 amount) { - return s.a[account].deposits[depositId].amount; - } - - /** - * @notice returns an array of amounts corresponding to Deposits. - */ - function balanceOfBatch( - address[] calldata accounts, - uint256[] calldata depositIds - ) external view returns (uint256[] memory) { - require(accounts.length == depositIds.length, "ERC1155: ids and amounts length mismatch"); - uint256[] memory balances = new uint256[](accounts.length); - for (uint256 i = 0; i < accounts.length; i++) { - balances[i] = s.a[accounts[i]].deposits[depositIds[i]].amount; - } - return balances; - } - - /** - * @notice outputs the depositID given an token address and stem. - */ - function getDepositId(address token, int96 stem) external pure returns (uint256) { - return LibBytes.packAddressAndStem(token, stem); - } } diff --git a/protocol/contracts/libraries/Silo/LibSilo.sol b/protocol/contracts/libraries/Silo/LibSilo.sol index 14801a9cef..e64a55e9e5 100644 --- a/protocol/contracts/libraries/Silo/LibSilo.sol +++ b/protocol/contracts/libraries/Silo/LibSilo.sol @@ -657,4 +657,39 @@ library LibSilo { AppStorage storage s = LibAppStorage.diamondStorage(); return s.a[account].lastUpdate > 0 && s.a[account].lastUpdate < s.season.stemStartSeason; } + + /** + * @dev Internal function to compute `account` balance of Earned Beans. + * + * The number of Earned Beans is equal to the difference between: + * - the "expected" Stalk balance, determined from the account balance of + * Roots. + * - the "account" Stalk balance, stored in account storage. + * divided by the number of Stalk per Bean. + * The earned beans from the latest season + */ + function _balanceOfEarnedBeans(address account, uint256 accountStalk) + internal + view + returns (uint256 beans) { + AppStorage storage s = LibAppStorage.diamondStorage(); + // There will be no Roots before the first Deposit is made. + if (s.s.roots == 0) return 0; + + uint256 stalk = s.s.stalk + .mul(s.a[account].roots) + .div(s.s.roots); + + // Beanstalk rounds down when minting Roots. Thus, it is possible that + // balanceOfRoots / totalRoots * totalStalk < s.a[account].s.stalk. + // As `account` Earned Balance balance should never be negative, + // Beanstalk returns 0 instead. + if (stalk <= accountStalk) return 0; + + // Calculate Earned Stalk and convert to Earned Beans. + beans = (stalk - accountStalk).div(C.STALK_PER_BEAN); // Note: SafeMath is redundant here. + if (beans > s.earnedBeans) return s.earnedBeans; + + return beans; + } } diff --git a/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol b/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol index 3016f21ced..d4dec75535 100644 --- a/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol @@ -54,7 +54,7 @@ contract MockSiloFacet is SiloFacet { incrementTotalDepositedBDV(C.UNRIPE_LP, bdv); uint256 seeds = bdv.mul(LibLegacyTokenSilo.getSeedsPerToken(C.UNRIPE_LP)); - uint256 stalk = bdv.mul(s.ss[C.UNRIPE_LP].stalkIssuedPerBdv).add(stalkRewardLegacy(seeds, _season() - _s)); + uint256 stalk = bdv.mul(s.ss[C.UNRIPE_LP].stalkIssuedPerBdv).add(stalkRewardLegacy(seeds, s.season.current - _s)); // not germinating because this is a old deposit. LibSilo.mintStalk(msg.sender, stalk, LibGerminate.Germinate.NOT_GERMINATING); mintSeeds(msg.sender, seeds); @@ -72,7 +72,7 @@ contract MockSiloFacet is SiloFacet { incrementTotalDepositedBDV(C.UNRIPE_BEAN, partialAmount); uint256 seeds = partialAmount.mul(LibLegacyTokenSilo.getSeedsPerToken(C.UNRIPE_BEAN)); - uint256 stalk = partialAmount.mul(s.ss[C.UNRIPE_BEAN].stalkIssuedPerBdv).add(stalkRewardLegacy(seeds, _season() - _s)); + uint256 stalk = partialAmount.mul(s.ss[C.UNRIPE_BEAN].stalkIssuedPerBdv).add(stalkRewardLegacy(seeds, s.season.current - _s)); LibSilo.mintStalk(msg.sender, stalk, LibGerminate.Germinate.NOT_GERMINATING); mintSeeds(msg.sender, seeds); @@ -101,11 +101,11 @@ contract MockSiloFacet is SiloFacet { * - {SiloFacet-transferDeposit(s)} */ function _mowLegacy(address account) internal { - uint32 _lastUpdate = lastUpdate(account); + uint32 _lastUpdate = s.a[account].lastUpdate; // If `account` was already updated this Season, there's no Stalk to Mow. - // _lastUpdate > _season() should not be possible, but it is checked anyway. - if (_lastUpdate >= _season()) return; + // _lastUpdate > s.season.current should not be possible, but it is checked anyway. + if (_lastUpdate >= s.season.current) return; // Increments `plenty` for `account` if a Flood has occured. // Saves Rain Roots for `account` if it is Raining. @@ -117,7 +117,7 @@ contract MockSiloFacet is SiloFacet { // Reset timer so that Grown Stalk for a particular Season can only be // claimed one time. - s.a[account].lastUpdate = _season(); + s.a[account].lastUpdate = s.season.current; } function __mowLegacy(address account) private { @@ -135,7 +135,7 @@ contract MockSiloFacet is SiloFacet { } // If a Sop has occured since last update, calculate rewards and set last Sop. if (s.season.lastSopSeason > _lastUpdate) { - s.a[account].sop.plenty = balanceOfPlenty(account); + s.a[account].sop.plenty = LibSilo.balanceOfPlenty(account); s.a[account].lastSop = s.season.lastSop; } if (s.season.raining) { @@ -162,7 +162,7 @@ contract MockSiloFacet is SiloFacet { return LibLegacyTokenSilo.stalkReward( s.a[account].s.seeds, - _season() - lastUpdate(account) + s.season.current - s.a[account].lastUpdate ); } @@ -254,7 +254,7 @@ contract MockSiloFacet is SiloFacet { (uint256 seeds, uint256 stalk) = libTokenSiloDepositLegacy( account, token, - _season(), + s.season.current, amount ); LibSilo.mintStalk(account, stalk, LibGerminate.Germinate.NOT_GERMINATING); @@ -347,7 +347,7 @@ contract MockSiloFacet is SiloFacet { // emit WhitelistToken(token, selector, stalkEarnedPerSeason, stalkIssuedPerBdv); } - function getSeedsPerToken(address token) public pure override returns (uint256) { + function getSeedsPerToken(address token) public pure returns (uint256) { if (token == C.BEAN) { return 2; } else if (token == C.UNRIPE_BEAN) { diff --git a/protocol/lib/forge-std b/protocol/lib/forge-std deleted file mode 160000 index 4a79aca83f..0000000000 --- a/protocol/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4a79aca83f8075f8b1b4fe9153945fef08375630