diff --git a/protocol/README.md b/protocol/README.md index 91250b10d5..ef68ce71c8 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -51,9 +51,13 @@ _Note: The Beanstalk repo is a monorepo with induvidual projects inside it. See 1. Clone the repository and install dependencies +*If using Mac on Apple Silicon* +`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` + ```bash git clone https://github.com/BeanstalkFarms/Beanstalk cd Beanstalk/protocol +export YARN_IGNORE_NODE=1 yarn ``` diff --git a/protocol/contracts/C.sol b/protocol/contracts/C.sol index 4ce14220ce..78c2c21eae 100644 --- a/protocol/contracts/C.sol +++ b/protocol/contracts/C.sol @@ -7,6 +7,7 @@ import "./interfaces/IFertilizer.sol"; import "./interfaces/IProxyAdmin.sol"; import "./libraries/Decimal.sol"; import "./interfaces/IPipeline.sol"; +import {AppStorage, LibAppStorage} from "contracts/libraries/LibAppStorage.sol"; /** * @title C @@ -56,6 +57,13 @@ library C { // Special index to indicate the data to copy is the operator address. uint80 internal constant OPERATOR_COPY_INDEX = type(uint80).max - 1; + //////////////////// Fork //////////////////// + uint256 internal constant DEST_FIELD = 1; + + function getSeasonPeriod() internal pure returns (uint256) { + return CURRENT_SEASON_PERIOD; + } + function getRootsBase() internal pure returns (uint256) { return ROOTS_BASE; } @@ -79,4 +87,9 @@ library C { function pipeline() internal pure returns (IPipeline) { return IPipeline(PIPELINE); } + + function bean() internal view returns (address) { + AppStorage storage s = LibAppStorage.diamondStorage(); + return s.sys.tokens.bean; + } } diff --git a/protocol/contracts/beanstalk/ForkSystem/TransmitInFacet.sol b/protocol/contracts/beanstalk/ForkSystem/TransmitInFacet.sol new file mode 100644 index 0000000000..02dad8960e --- /dev/null +++ b/protocol/contracts/beanstalk/ForkSystem/TransmitInFacet.sol @@ -0,0 +1,40 @@ +/** + * SPDX-License-Identifier: MIT + **/ + +pragma solidity ^0.8.20; + +import {AppStorage} from "contracts/beanstalk/storage/AppStorage.sol"; +import {Invariable} from "contracts/beanstalk/Invariable.sol"; +import {LibTransmitIn} from "contracts/libraries/ForkSystem/LibTransmitIn.sol"; + +/** + * @title TransmitInFacet + * @author funderbrker + * @notice Destination instance logic for receiving transmitted assets from another version. + * @notice Destination has knowledge of valid Sources and their configurations at deployment time. + **/ +contract TransmitInFacet is Invariable { + AppStorage internal s; + + /** + * @notice Process the inbound migration locally. + * @dev Reverts if failure to mint assets or handle migration in. + * @dev Arguments are bytes because different sources may use different encodings. + */ + function transmitIn( + address user, + bytes[][] calldata assets, + bytes calldata //data + ) external fundsSafu { + require(s.sys.supportedSourceForks[msg.sender], "Unsupported source"); + require(assets.length >= 2, "Insufficient data provided"); + + LibTransmitIn.transmitInDeposits(user, assets[0]); + LibTransmitIn.transmitInPlots(user, assets[1]); + if (assets.length > 2) { + LibTransmitIn.transmitInFertilizer(user, assets[2]); + } + // additional assets can be processed here in the future + } +} diff --git a/protocol/contracts/beanstalk/ForkSystem/TransmitOutFacet.sol b/protocol/contracts/beanstalk/ForkSystem/TransmitOutFacet.sol new file mode 100644 index 0000000000..f48ac5fc89 --- /dev/null +++ b/protocol/contracts/beanstalk/ForkSystem/TransmitOutFacet.sol @@ -0,0 +1,54 @@ +/** + * SPDX-License-Identifier: MIT + **/ + +pragma solidity ^0.8.20; + +import {Invariable} from "contracts/beanstalk/Invariable.sol"; +import {LibTractor} from "contracts/libraries/LibTractor.sol"; +import {LibTransmitOut} from "contracts/libraries/ForkSystem/LibTransmitOut.sol"; +import {ITransmitInFacet} from "contracts/interfaces/ITransmitInFacet.sol"; + +/** + * @title TransmitOutFacet + * @author funderbrker + * @notice Source instance logic for migrating assets to new version. + * @notice Source instance has no knowledge of possible Destinations or their configurations. + **/ +contract TransmitOutFacet is Invariable { + /** + * @notice Process the outbound migration and transfer necessary assets to destination. + * @dev Reverts if failure to burn assets or destination fails. + * @param assets Contains abi encoded deposits, plots, and fertilizer. + * @param data Currently unused but remains available for paramaters such as minimum output requirements. + */ + function transmitOut( + address destination, + bytes[] calldata assets, + bytes calldata data + ) external fundsSafu { + require(assets.length >= 3, "Missing asset data"); + + bytes[] memory deposits = LibTransmitOut.transmitOutDeposits( + LibTractor._user(), + destination, + abi.decode(assets[0], (LibTransmitOut.SourceDeposit[])) + ); + bytes[] memory plots = LibTransmitOut.transmitOutPlots( + LibTractor._user(), + abi.decode(assets[1], (LibTransmitOut.SourcePlot[])) + ); + bytes[] memory fertilizer = LibTransmitOut.transmitOutFertilizer( + LibTractor._user(), + abi.decode(assets[2], (LibTransmitOut.SourceFertilizer[])) + ); + + bytes[][] memory processedAssets = new bytes[][](3); + processedAssets[0] = deposits; + processedAssets[1] = plots; + processedAssets[2] = fertilizer; + + // Reverts if Destination fails to handle transmitted assets. + ITransmitInFacet(destination).transmitIn(LibTractor._user(), processedAssets, data); + } +} diff --git a/protocol/contracts/beanstalk/Invariable.sol b/protocol/contracts/beanstalk/Invariable.sol index ba38e3307d..8bf14c7d04 100644 --- a/protocol/contracts/beanstalk/Invariable.sol +++ b/protocol/contracts/beanstalk/Invariable.sol @@ -195,7 +195,7 @@ abstract contract Invariable { s.sys.silo.unripeSettings[s.sys.tokens.urBean].balanceOfUnderlying + // unchopped underlying beans s.sys.orderLockedBeans; for (uint256 j; j < s.sys.fieldCount; j++) { - entitlements[i] += (s.sys.fields[j].harvestable - s.sys.fields[j].harvested); // unharvested harvestable beans + entitlements[i] += (s.sys.fields[j].harvestable - s.sys.fields[j].processed); // unharvested harvestable beans } } else if (tokens[i] == LibUnripe._getUnderlyingToken(s.sys.tokens.urLp)) { entitlements[i] += s.sys.silo.unripeSettings[s.sys.tokens.urLp].balanceOfUnderlying; diff --git a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol index 643b73a6c9..6f51076199 100644 --- a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol +++ b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol @@ -47,13 +47,7 @@ contract FertilizerFacet is Invariable, ReentrancyGuard { uint256[] calldata ids, LibTransfer.To mode ) external payable fundsSafu noSupplyChange oneOutFlow(s.sys.tokens.bean) nonReentrant { - uint256 amount = IFertilizer(s.sys.tokens.fertilizer).beanstalkUpdate( - LibTractor._user(), - ids, - s.sys.fert.bpf - ); - s.sys.fert.fertilizedPaidIndex += amount; - LibTransfer.sendToken(BeanstalkERC20(s.sys.tokens.bean), amount, LibTractor._user(), mode); + LibFertilizer.claimFertilized(ids, mode); } /** diff --git a/protocol/contracts/beanstalk/field/FieldFacet.sol b/protocol/contracts/beanstalk/field/FieldFacet.sol index b80d9be2fe..4d19467770 100644 --- a/protocol/contracts/beanstalk/field/FieldFacet.sol +++ b/protocol/contracts/beanstalk/field/FieldFacet.sol @@ -15,6 +15,7 @@ import {ReentrancyGuard} from "../ReentrancyGuard.sol"; import {Invariable} from "contracts/beanstalk/Invariable.sol"; import {LibDiamond} from "contracts/libraries/LibDiamond.sol"; import {LibMarket} from "contracts/libraries/LibMarket.sol"; +import {LibField} from "contracts/libraries/LibField.sol"; import {BeanstalkERC20} from "contracts/tokens/ERC20/BeanstalkERC20.sol"; interface IBeanstalk { @@ -145,6 +146,7 @@ contract FieldFacet is Invariable, ReentrancyGuard { * * Pods are "burned" when the corresponding Plot is deleted from * `s.accts[account].fields[fieldId].plots`. + * @dev If Plot has been Slashed, burn the Plot. Anyone can burn Slashed Plots. */ function harvest( uint256 fieldId, @@ -163,6 +165,7 @@ contract FieldFacet is Invariable, ReentrancyGuard { /** * @dev Ensure that each Plot is at least partially harvestable, burn the Plot, * update the total harvested, and emit a {Harvest} event. + * @dev If Plot has been Slashed, burn the Plot. */ function _harvest( uint256 fieldId, @@ -172,48 +175,55 @@ contract FieldFacet is Invariable, ReentrancyGuard { // The Plot is partially harvestable if its index is less than // the current harvestable index. require(plots[i] < s.sys.fields[fieldId].harvestable, "Field: Plot not Harvestable"); - uint256 harvested = _harvestPlot(LibTractor._user(), fieldId, plots[i]); - beansHarvested += harvested; + beansHarvested += _harvestPlot(LibTractor._user(), fieldId, plots[i]); } - s.sys.fields[fieldId].harvested += beansHarvested; + s.sys.fields[fieldId].totalHarvested += beansHarvested; emit Harvest(LibTractor._user(), fieldId, plots, beansHarvested); } /** * @dev Check if a Plot is at least partially Harvestable; calculate how many * Pods are Harvestable, create a new Plot if necessary. + * @dev If Plot has been Slashed, burn the Plot. */ function _harvestPlot( address account, uint256 fieldId, uint256 index - ) private returns (uint256 harvestablePods) { - // Check that `account` holds this Plot. - uint256 pods = s.accts[account].fields[fieldId].plots[index]; - require(pods > 0, "Field: no plot"); + ) private returns (uint256 beansHarvested) { + // If Plot held by null address, it has been Slashed. Burn Plot. + if (s.accts[address(0)].fields[fieldId].plots[index] > 0) { + account = address(0); + } + uint256 plotPods = s.accts[account].fields[fieldId].plots[index]; + require(plotPods > 0, "Field: no plot"); // Calculate how many Pods are harvestable. // The upstream _harvest function checks that at least some Pods // are harvestable. - harvestablePods = s.sys.fields[fieldId].harvestable.sub(index); + uint256 harvestablePods = s.sys.fields[fieldId].harvestable.sub(index); + + LibMarket._cancelPodListing(account, fieldId, index); - LibMarket._cancelPodListing(LibTractor._user(), fieldId, index); + LibField.deletePlot(account, fieldId, index); - delete s.accts[account].fields[fieldId].plots[index]; - LibDibbler.removePlotIndexFromAccount(account, fieldId, index); + beansHarvested = plotPods <= harvestablePods ? plotPods : harvestablePods; + s.sys.fields[fieldId].processed += beansHarvested; + + // If burning a Slashed Plot, amount harvestable does not change. + if (account == address(0)) { + s.sys.fields[fieldId].harvestable += beansHarvested; + return 0; + } // If the entire Plot was harvested, exit. - if (harvestablePods >= pods) { - return pods; + if (beansHarvested == plotPods) { + return beansHarvested; } // Create a new Plot with remaining Pods. - uint256 newIndex = index.add(harvestablePods); - s.accts[account].fields[fieldId].plots[newIndex] = pods.sub(harvestablePods); - s.accts[account].fields[fieldId].plotIndexes.push(newIndex); - s.accts[account].fields[fieldId].piIndex[newIndex] = - s.accts[account].fields[fieldId].plotIndexes.length - - 1; + uint256 newIndex = index.add(beansHarvested); + LibField.createPlot(account, fieldId, newIndex, plotPods.sub(beansHarvested)); } //////////////////// CONFIG ///////////////////// @@ -270,19 +280,35 @@ contract FieldFacet is Invariable, ReentrancyGuard { /** * @notice Returns the number of outstanding Pods. Includes Pods that are - * currently Harvestable but have not yet been Harvested. + * currently Harvestable or Burnable but have not yet been Harvested. * @param fieldId The index of the Field to query. */ function totalPods(uint256 fieldId) public view returns (uint256) { - return s.sys.fields[fieldId].pods - s.sys.fields[fieldId].harvested; + return s.sys.fields[fieldId].pods - s.sys.fields[fieldId].processed; } /** - * @notice Returns the number of Pods that have ever been Harvested. + * @notice Returns the number of Pods that have ever been Harvested or Burned. + * @param fieldId The index of the Field to query. + */ + function totalProcessed(uint256 fieldId) public view returns (uint256) { + return s.sys.fields[fieldId].processed; + } + + /** + * @notice Returns the number of Pods that have been Harvested. Not including Slashed plots. * @param fieldId The index of the Field to query. */ function totalHarvested(uint256 fieldId) public view returns (uint256) { - return s.sys.fields[fieldId].harvested; + return s.sys.fields[fieldId].totalHarvested; + } + + /** + * @notice Returns the number of Pods that have been Slashed. + * @param fieldId The index of the Field to query. + */ + function totalSlashed(uint256 fieldId) public view returns (uint256) { + return s.sys.fields[fieldId].processed - s.sys.fields[fieldId].totalHarvested; } /** @@ -290,22 +316,24 @@ contract FieldFacet is Invariable, ReentrancyGuard { * have not yet been Harvested. * @dev This is the number of Pods that Beanstalk is prepared to pay back, * but that haven’t yet been claimed via the `harvest()` function. + * @dev Cannot use this number as an index, as there is no accounting for Slashed plots. * @param fieldId The index of the Field to query. */ function totalHarvestable(uint256 fieldId) public view returns (uint256) { - return s.sys.fields[fieldId].harvestable - s.sys.fields[fieldId].harvested; + return s.sys.fields[fieldId].harvestable - s.sys.fields[fieldId].processed; } /** * @notice Returns the number of Pods that are currently Harvestable for the active Field. + * @dev Cannot use this number as an index, as there is no accounting for Slashed plots. */ function totalHarvestableForActiveField() public view returns (uint256) { - return - s.sys.fields[s.sys.activeField].harvestable - s.sys.fields[s.sys.activeField].harvested; + return totalHarvestable(s.sys.activeField); } /** * @notice Returns the number of Pods that are not yet Harvestable. Also known as the Pod Line. + * @dev Includes Slashed Pods. * @param fieldId The index of the Field to query. */ function totalUnharvestable(uint256 fieldId) public view returns (uint256) { diff --git a/protocol/contracts/beanstalk/init/InitDiamond.sol b/protocol/contracts/beanstalk/init/InitDiamond.sol new file mode 100644 index 0000000000..4aee038cf1 --- /dev/null +++ b/protocol/contracts/beanstalk/init/InitDiamond.sol @@ -0,0 +1,28 @@ +/* + SPDX-License-Identifier: MIT +*/ + +pragma solidity ^0.8.20; + +import {InitializeDiamond} from "contracts/beanstalk/init/InitializeDiamond.sol"; +import {C} from "contracts/C.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; + +/** + * @author Publius, Brean + * @title InitDiamond + * @notice InitDiamond initializes the Beanstalk Diamond. + * A new bean token and bean:TOKEN well are deployed. + * + **/ +contract InitDiamond is InitializeDiamond { + // initial reward for deploying beanstalk. + uint256 constant INIT_SUPPLY = 100e6; + + function init() external { + initializeDiamond(LibConstant.BEAN, LibConstant.BEAN_ETH_WELL); + + IBean(s.sys.tokens.bean).mint(msg.sender, INIT_SUPPLY); + } +} diff --git a/protocol/contracts/beanstalk/init/InitDistribution.sol b/protocol/contracts/beanstalk/init/InitDistribution.sol index 956a54f8a5..9504a11b7c 100644 --- a/protocol/contracts/beanstalk/init/InitDistribution.sol +++ b/protocol/contracts/beanstalk/init/InitDistribution.sol @@ -29,6 +29,8 @@ contract InitDistribution { AppStorage internal s; IBeanstalk beanstalk; + uint256 internal constant ACTIVE_FIELD = 0; + function init(address shipmentPlanner) external { beanstalk = IBeanstalk(address(this)); require( @@ -48,7 +50,7 @@ contract InitDistribution { shipmentPlanner, ShipmentPlanner.getFieldPlan.selector, ShipmentRecipient.FIELD, - abi.encode(uint256(0)) + abi.encode(ACTIVE_FIELD) ); shipmentRoutes[2] = ShipmentRoute( @@ -60,7 +62,7 @@ contract InitDistribution { beanstalk.setShipmentRoutes(shipmentRoutes); beanstalk.addField(); - beanstalk.setActiveField(0, 1); + beanstalk.setActiveField(ACTIVE_FIELD, 1); // TODO: Initialize Field values from priors. } diff --git a/protocol/contracts/beanstalk/init/InitalizeDiamond.sol b/protocol/contracts/beanstalk/init/InitializeDiamond.sol similarity index 85% rename from protocol/contracts/beanstalk/init/InitalizeDiamond.sol rename to protocol/contracts/beanstalk/init/InitializeDiamond.sol index c88de226c3..fea3538b4a 100644 --- a/protocol/contracts/beanstalk/init/InitalizeDiamond.sol +++ b/protocol/contracts/beanstalk/init/InitializeDiamond.sol @@ -20,11 +20,11 @@ import {C} from "contracts/C.sol"; /** * @author Publius, Brean - * @title InitalizeDiamond - * @notice InitalizeDiamond provides helper functions to initalize beanstalk. + * @title InitializeDiamond + * @notice InitializeDiamond provides helper functions to initialize beanstalk. **/ -contract InitalizeDiamond { +contract InitializeDiamond { AppStorage internal s; // INITIAL CONSTANTS // @@ -71,25 +71,25 @@ contract InitalizeDiamond { event BeanToMaxLpGpPerBdvRatioChange(uint256 indexed season, uint256 caseId, int80 absChange); /** - * @notice Initalizes the diamond with base conditions. - * @dev the base initalization initalizes various parameters, + * @notice Initializes the diamond with base conditions. + * @dev the base initalization initializes various parameters, * as well as whitelists the bean and bean:TKN pools. */ - function initalizeDiamond(address bean, address beanTokenWell) internal { + function initializeDiamond(address bean, address beanTokenWell) internal { addInterfaces(); initializeTokens(bean); - initalizeSeason(); - initalizeField(); - initalizeFarmAndTractor(); - initalizeSilo(uint16(s.sys.season.current)); - initalizeSeedGauge(INIT_BEAN_TO_MAX_LP_GP_RATIO, INIT_AVG_GSPBDV); + initializeSeason(); + initializeField(); + initializeFarmAndTractor(); + initializeSilo(uint16(s.sys.season.current)); + initializeSeedGauge(INIT_BEAN_TO_MAX_LP_GP_RATIO, INIT_AVG_GSPBDV); address[] memory tokens = new address[](2); tokens[0] = bean; tokens[1] = beanTokenWell; // note: bean and assets that are not in the gauge system - // do not need to initalize the gauge system. + // do not need to initialize the gauge system. Implementation memory impl = Implementation(address(0), bytes4(0), bytes1(0), new bytes(0)); Implementation memory liquidityWeightImpl = Implementation( address(0), @@ -143,6 +143,10 @@ contract InitalizeDiamond { // init tractor. LibTractor._tractorStorage().activePublisher = payable(address(1)); + + // Set the sources that can migrate into this Beanstalk. + // TODO: Change this based on each fork instance. + // s.sys.supportedSourceForks[address()] = true; } /** @@ -160,35 +164,35 @@ contract InitalizeDiamond { } /** - * @notice Initalizes field parameters. + * @notice Initializes field parameters. */ - function initalizeField() internal { + function initializeField() internal { s.sys.weather.temp = 1; s.sys.weather.thisSowTime = type(uint32).max; s.sys.weather.lastSowTime = type(uint32).max; } /** - * @notice Initalizes season parameters. + * @notice Initializes season parameters. */ - function initalizeSeason() internal { + function initializeSeason() internal { // set current season to 1. s.sys.season.current = 1; - // initalize the duration of 1 season in seconds. - s.sys.season.period = C.CURRENT_SEASON_PERIOD; + // initialize the duration of 1 season in seconds. + s.sys.season.period = C.getSeasonPeriod(); - // initalize current timestamp. + // initialize current timestamp. s.sys.season.timestamp = block.timestamp; - // initalize the start timestamp. + // initialize the start timestamp. // Rounds down to the nearest hour // if needed. s.sys.season.start = s.sys.season.period > 0 ? (block.timestamp / s.sys.season.period) * s.sys.season.period : block.timestamp; - // initalizes the cases that beanstalk uses + // initializes the cases that beanstalk uses // to change certain parameters of itself. setCases(); @@ -196,29 +200,29 @@ contract InitalizeDiamond { } /** - * @notice Initalize the cases for the diamond. + * @notice Initialize the cases for the diamond. */ function setCases() internal { LibCases.setCasesV2(); } /** - * Initalizes silo parameters. + * Initializes silo parameters. */ - function initalizeSilo(uint16 season) internal { - // initalize when the silo started silo V3. + function initializeSilo(uint16 season) internal { + // initialize when the silo started silo V3. s.sys.season.stemStartSeason = season; s.sys.season.stemScaleSeason = season; } - function initalizeSeedGauge( + function initializeSeedGauge( uint128 beanToMaxLpGpRatio, uint128 averageGrownStalkPerBdvPerSeason ) internal { - // initalize the ratio of bean to max lp gp per bdv. + // initialize the ratio of bean to max lp gp per bdv. s.sys.seedGauge.beanToMaxLpGpPerBdvRatio = beanToMaxLpGpRatio; - // initalize the average grown stalk per bdv per season. + // initialize the average grown stalk per bdv per season. s.sys.seedGauge.averageGrownStalkPerBdvPerSeason = averageGrownStalkPerBdvPerSeason; // emit events. @@ -281,7 +285,7 @@ contract InitalizeDiamond { s.sys.evaluationParameters.baseReward = BASE_REWARD; } - function initalizeFarmAndTractor() internal { + function initializeFarmAndTractor() internal { LibTractor._resetPublisher(); LibTractor._setVersion("1.0.0"); } diff --git a/protocol/contracts/beanstalk/init/newInitDiamond.sol b/protocol/contracts/beanstalk/init/newInitDiamond.sol index e148f99974..a878b106ac 100644 --- a/protocol/contracts/beanstalk/init/newInitDiamond.sol +++ b/protocol/contracts/beanstalk/init/newInitDiamond.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; -import {InitalizeDiamond} from "contracts/beanstalk/init/InitalizeDiamond.sol"; +import {InitializeDiamond} from "contracts/beanstalk/init/InitializeDiamond.sol"; import {BeanstalkERC20} from "contracts/tokens/ERC20/BeanstalkERC20.sol"; /** @@ -14,7 +14,7 @@ import {BeanstalkERC20} from "contracts/tokens/ERC20/BeanstalkERC20.sol"; * A new bean token and bean:TOKEN well are deployed. * **/ -contract InitDiamond is InitalizeDiamond { +contract InitDiamond is InitializeDiamond { // Tokens address internal constant BEAN = address(0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab); address internal constant BEAN_ETH_WELL = address(0xBEA0e11282e2bB5893bEcE110cF199501e872bAd); @@ -23,7 +23,7 @@ contract InitDiamond is InitalizeDiamond { uint256 internal constant INIT_SUPPLY = 100e6; function init() external { - initalizeDiamond(BEAN, BEAN_ETH_WELL); + initializeDiamond(BEAN, BEAN_ETH_WELL); BeanstalkERC20(BEAN).mint(msg.sender, INIT_SUPPLY); } diff --git a/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobal.sol b/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobal.sol index 9b94dbf371..7ab769e97e 100644 --- a/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobal.sol +++ b/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobal.sol @@ -97,7 +97,7 @@ contract ReseedGlobal is Distribution { s.sys.rain = system.r; s.sys.evaluationParameters = system.ep; - // initalize tractor: + // initialize tractor: setTractor(); } diff --git a/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobalRevised.sol b/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobalRevised.sol index b813d74d39..4131e01cb6 100644 --- a/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobalRevised.sol +++ b/protocol/contracts/beanstalk/init/reseed/L2/ReseedGlobalRevised.sol @@ -97,7 +97,7 @@ contract ReseedGlobalRevised is Distribution { // s.sys.rain = system.r; // s.sys.evaluationParameters = system.ep; - // initalize tractor: + // initialize tractor: // setTractor(); } diff --git a/protocol/contracts/beanstalk/market/MarketplaceFacet/PodTransfer.sol b/protocol/contracts/beanstalk/market/MarketplaceFacet/PodTransfer.sol index 40762ae63c..e87fc7580f 100644 --- a/protocol/contracts/beanstalk/market/MarketplaceFacet/PodTransfer.sol +++ b/protocol/contracts/beanstalk/market/MarketplaceFacet/PodTransfer.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; import {ReentrancyGuard} from "contracts/beanstalk/ReentrancyGuard.sol"; -import {LibDibbler} from "contracts/libraries/LibDibbler.sol"; +import {LibField} from "contracts/libraries/LibField.sol"; import {C} from "contracts/C.sol"; /** @@ -55,19 +55,11 @@ contract PodTransfer is ReentrancyGuard { ) internal { require(from != to, "Field: Cannot transfer Pods to oneself."); require(amount > 0, "Marketplace: amount must be > 0."); - insertPlot(to, fieldId, index + start, amount); + LibField.createPlot(to, fieldId, index + start, amount); removePlot(from, fieldId, index, start, amount + start); emit PlotTransfer(from, to, fieldId, index + start, amount); } - function insertPlot(address account, uint256 fieldId, uint256 index, uint256 amount) internal { - s.accts[account].fields[fieldId].plots[index] = amount; - s.accts[account].fields[fieldId].plotIndexes.push(index); - s.accts[account].fields[fieldId].piIndex[index] = - s.accts[account].fields[fieldId].plotIndexes.length - - 1; - } - function removePlot( address account, uint256 fieldId, @@ -80,17 +72,12 @@ contract PodTransfer is ReentrancyGuard { if (start > 0) { s.accts[account].fields[fieldId].plots[index] = start; } else { - delete s.accts[account].fields[fieldId].plots[index]; - LibDibbler.removePlotIndexFromAccount(account, fieldId, index); + LibField.deletePlot(account, fieldId, index); } if (amountAfterEnd > 0) { uint256 newIndex = index + end; - s.accts[account].fields[fieldId].plots[newIndex] = amountAfterEnd; - s.accts[account].fields[fieldId].plotIndexes.push(newIndex); - s.accts[account].fields[fieldId].piIndex[newIndex] = - s.accts[account].fields[fieldId].plotIndexes.length - - 1; + LibField.createPlot(account, fieldId, newIndex, amountAfterEnd); } } diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol b/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol index ce7f051501..f3cc7f36e3 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol @@ -84,7 +84,7 @@ contract SiloFacet is Invariable, TokenSilo { uint256 amount, LibTransfer.To mode ) external payable fundsSafu noSupplyChange oneOutFlow(token) mowSender(token) nonReentrant { - _withdrawDeposit(LibTractor._user(), token, stem, amount); + LibSilo._withdrawDeposit(LibTractor._user(), token, stem, amount); LibTransfer.sendToken(IERC20(token), amount, LibTractor._user(), mode); } @@ -108,7 +108,7 @@ contract SiloFacet is Invariable, TokenSilo { uint256[] calldata amounts, LibTransfer.To mode ) external payable fundsSafu noSupplyChange oneOutFlow(token) mowSender(token) nonReentrant { - uint256 amount = _withdrawDeposits(LibTractor._user(), token, stems, amounts); + uint256 amount = LibSilo._withdrawDeposits(LibTractor._user(), token, stems, amounts); LibTransfer.sendToken(IERC20(token), amount, LibTractor._user(), mode); } diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol b/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol index 434af462ad..cc4a73053d 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol @@ -175,176 +175,6 @@ contract TokenSilo is ReentrancyGuard { LibSilo.mintGerminatingStalk(account, uint128(stalk), side); } - //////////////////////// WITHDRAW //////////////////////// - - /** - * @notice Handles withdraw accounting. - * - * - {LibSilo._removeDepositFromAccount} calculates the stalk - * assoicated with a given deposit, and removes the deposit from the account. - * emits `RemoveDeposit` and `TransferSingle` events. - * - * - {_withdraw} updates the total value deposited in the silo, and burns - * the stalk assoicated with the deposits. - * - */ - function _withdrawDeposit(address account, address token, int96 stem, uint256 amount) internal { - // Remove the Deposit from `account`. - ( - uint256 initalStalkRemoved, - uint256 grownStalkRemoved, - uint256 bdvRemoved, - GerminationSide side - ) = LibSilo._removeDepositFromAccount( - account, - token, - stem, - amount, - LibTokenSilo.Transfer.emitTransferSingle - ); - if (side == GerminationSide.NOT_GERMINATING) { - // remove the deposit from totals - _withdraw( - account, - token, - amount, - bdvRemoved, - initalStalkRemoved.add(grownStalkRemoved) - ); - } else { - // remove deposit from germination, and burn the grown stalk. - // grown stalk does not germinate and is not counted in germinating totals. - _withdrawGerminating(account, token, amount, bdvRemoved, initalStalkRemoved, side); - - if (grownStalkRemoved > 0) { - LibSilo.burnActiveStalk(account, grownStalkRemoved); - } - } - } - - /** - * @notice Handles withdraw accounting for multiple deposits. - * - * - {LibSilo._removeDepositsFromAccount} removes the deposits from the account, - * and returns the total tokens, stalk, and bdv removed from the account. - * - * - {_withdraw} updates the total value deposited in the silo, and burns - * the stalk assoicated with the deposits. - * - */ - function _withdrawDeposits( - address account, - address token, - int96[] calldata stems, - uint256[] calldata amounts - ) internal returns (uint256) { - require(stems.length == amounts.length, "Silo: Crates, amounts are diff lengths."); - - LibSilo.AssetsRemoved memory ar = LibSilo._removeDepositsFromAccount( - account, - token, - stems, - amounts, - LibSilo.ERC1155Event.EMIT_BATCH_EVENT - ); - - // withdraw deposits that are not germinating. - if (ar.active.tokens > 0) { - _withdraw(account, token, ar.active.tokens, ar.active.bdv, ar.active.stalk); - } - - // withdraw Germinating deposits from odd seasons - if (ar.odd.tokens > 0) { - _withdrawGerminating( - account, - token, - ar.odd.tokens, - ar.odd.bdv, - ar.odd.stalk, - GerminationSide.ODD - ); - } - - // withdraw Germinating deposits from even seasons - if (ar.even.tokens > 0) { - _withdrawGerminating( - account, - token, - ar.even.tokens, - ar.even.bdv, - ar.even.stalk, - GerminationSide.EVEN - ); - } - - if (ar.grownStalkFromGermDeposits > 0) { - LibSilo.burnActiveStalk(account, ar.grownStalkFromGermDeposits); - } - - // we return the summation of all tokens removed from the silo. - // to be used in {SiloFacet.withdrawDeposits}. - return ar.active.tokens.add(ar.odd.tokens).add(ar.even.tokens); - } - - /** - * @dev internal helper function for withdraw accounting. - */ - function _withdraw( - address account, - address token, - uint256 amount, - uint256 bdv, - uint256 stalk - ) private { - // Decrement total deposited in the silo. - LibTokenSilo.decrementTotalDeposited(token, amount, bdv); - // Burn stalk and roots associated with the stalk. - LibSilo.burnActiveStalk(account, stalk); - } - - /** - * @dev internal helper function for withdraw accounting with germination. - * @param side determines whether to withdraw from odd or even germination. - */ - function _withdrawGerminating( - address account, - address token, - uint256 amount, - uint256 bdv, - uint256 stalk, - GerminationSide side - ) private { - // Deposited Earned Beans do not germinate. Thus, when withdrawing a Bean Deposit - // with a Germinating Stem, Beanstalk needs to determine how many of the Beans - // were Planted vs Deposited from a Circulating/Farm balance. - // If a Farmer's Germinating Stalk for a given Season is less than the number of - // Deposited Beans in that Season, then it is assumed that the excess Beans were - // Planted. - if (token == s.sys.tokens.bean) { - (uint256 germinatingStalk, uint256 earnedBeansStalk) = LibSilo.checkForEarnedBeans( - account, - stalk, - side - ); - // set the bdv and amount accordingly to the stalk. - stalk = germinatingStalk; - uint256 earnedBeans = earnedBeansStalk.div(C.STALK_PER_BEAN); - amount = amount - earnedBeans; - // note: the 1 Bean = 1 BDV assumption cannot be made here for input `bdv`, - // as a user converting a germinating LP deposit into bean may have an inflated bdv. - // thus, amount and bdv are decremented by the earnedBeans (where the 1 Bean = 1 BDV assumption can be made). - bdv = bdv - earnedBeans; - - // burn the earned bean stalk (which is active). - LibSilo.burnActiveStalk(account, earnedBeansStalk); - // calculate earnedBeans and decrement totalDeposited. - LibTokenSilo.decrementTotalDeposited(s.sys.tokens.bean, earnedBeans, earnedBeans); - } - // Decrement from total germinating. - LibTokenSilo.decrementTotalGerminating(token, amount, bdv, side); // Decrement total Germinating in the silo. - LibSilo.burnGerminatingStalk(account, uint128(stalk), side); // Burn stalk and roots associated with the stalk. - } - //////////////////////// TRANSFER //////////////////////// /** diff --git a/protocol/contracts/beanstalk/storage/Account.sol b/protocol/contracts/beanstalk/storage/Account.sol index e32eae4cc5..0fbbb48984 100644 --- a/protocol/contracts/beanstalk/storage/Account.sol +++ b/protocol/contracts/beanstalk/storage/Account.sol @@ -55,7 +55,7 @@ struct Account { * @notice Stores a Farmer's Plots and Pod allowances. * @param plots A Farmer's Plots. Maps from Plot index to Pod amount. * @param podAllowances An allowance mapping for Pods similar to that of the ERC-20 standard. Maps from spender address to allowance amount. - * @param plotIndexes An array of Plot indexes. Used to return the farm plots of a Farmer. + * @param plotIndexes An array of Plot indexes. Used to return the farm plots of a Farmer. Not tracked for null address. * @param piIndex A mapping from Plot index to the index in plotIndexes. * @param _buffer Reserved storage for future additions. */ diff --git a/protocol/contracts/beanstalk/storage/System.sol b/protocol/contracts/beanstalk/storage/System.sol index 0d2b062f7d..afa635b242 100644 --- a/protocol/contracts/beanstalk/storage/System.sol +++ b/protocol/contracts/beanstalk/storage/System.sol @@ -38,9 +38,10 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; * @param weather See {Weather}. * @param seedGauge Stores the seedGauge. * @param rain See {Rain}. - * @param migration See {Migration}. + * @param l2Migration See {L2Migration}. * @param evaluationParameters See {EvaluationParameters}. * @param sop See {SeasonOfPlenty}. + * @param supportedSourceForks a mapping of addresses that can transmit into this Beanstalk instance. */ struct System { bool paused; @@ -76,7 +77,7 @@ struct System { L2Migration l2Migration; EvaluationParameters evaluationParameters; SeasonOfPlenty sop; - // A buffer is not included here, bc current layout of AppStorage makes it unnecessary. + mapping(address => bool) supportedSourceForks; } /** @@ -122,15 +123,23 @@ struct Silo { /** * @notice System-level Field state variables. * @param pods The pod index; the total number of Pods ever minted. - * @param harvested The harvested index; the total number of Pods that have ever been Harvested. - * @param harvestable The harvestable index; the total number of Pods that have ever been Harvestable. Included previously Harvested Beans. + * @param processed Amount of Pods that have ever been Harvested or Slashed. + * @param harvestable Index of Pods that have ever been Harvestable. Included Processed Pods. + * @param latestTransmittedPlotIndex The index of the latest Plot that has been transmitted in. + * @param latestTransmittedPlotOwner The owner of the latest Plot that has been transmitted in. + * @param srcInitPods The amount of pods in source beanstalk field at destination deployment. + * @param totalHarvested The number of Pods that have been Harvested. Not including Slashed plots. * @param _buffer Reserved storage for future expansion. */ struct Field { uint256 pods; - uint256 harvested; + uint256 processed; uint256 harvestable; - bytes32[8] _buffer; + uint256 latestTransmittedPlotIndex; + address latestTransmittedPlotOwner; + uint256 srcInitPods; + uint256 totalHarvested; + bytes32[4] _buffer; } /** diff --git a/protocol/contracts/ecosystem/ShipmentPlanner.sol b/protocol/contracts/ecosystem/ShipmentPlanner.sol index a75e768155..8b529d1733 100644 --- a/protocol/contracts/ecosystem/ShipmentPlanner.sol +++ b/protocol/contracts/ecosystem/ShipmentPlanner.sol @@ -69,6 +69,7 @@ contract ShipmentPlanner { uint256 fieldId = abi.decode(data, (uint256)); require(fieldId < beanstalk.fieldCount(), "Field does not exist"); if (!beanstalk.isHarvesting(fieldId)) return shipmentPlan; + // TODO: Set to handle multiple fields, if needed. return ShipmentPlan({points: FIELD_POINTS, cap: beanstalk.totalUnharvestable(fieldId)}); } diff --git a/protocol/contracts/interfaces/IFertilizer.sol b/protocol/contracts/interfaces/IFertilizer.sol index febb564d20..9f9ba49891 100644 --- a/protocol/contracts/interfaces/IFertilizer.sol +++ b/protocol/contracts/interfaces/IFertilizer.sol @@ -13,6 +13,8 @@ interface IFertilizer { uint128 bpf ) external returns (uint256); function beanstalkMint(address account, uint256 id, uint128 amount, uint128 bpf) external; + function beanstalkBurn(address account, uint256 id, uint128 amount, uint128 bpf) external; + function balanceOf(address account, uint256 id) external view returns (uint256); function balanceOfFertilized( address account, uint256[] memory ids diff --git a/protocol/contracts/interfaces/IMockFBeanstalk.sol b/protocol/contracts/interfaces/IMockFBeanstalk.sol index ecefa22358..22dcb946f4 100644 --- a/protocol/contracts/interfaces/IMockFBeanstalk.sol +++ b/protocol/contracts/interfaces/IMockFBeanstalk.sol @@ -259,6 +259,32 @@ interface IMockFBeanstalk { bool isSoppable; } + struct SourceDeposit { + address token; + uint256 amount; + int96 stem; + uint256[] sourceMinTokenAmountsOut; // LP only + uint256 destMinLpOut; // LP only + uint256 lpDeadline; + uint256 _grownStalk; // not stalk // need to change logic + uint256 _burnedBeans; + address _transferredToken; // NOTE what if LP type is not supported at destination? + uint256 _transferredTokenAmount; + } + + struct SourcePlot { + uint256 fieldId; + uint256 index; + uint256 amount; + uint256 existingIndex; + } + + struct SourceFertilizer { + uint128 id; + uint256 amount; + uint128 _remainingBpf; + } + error AddressEmptyCode(address target); error AddressInsufficientBalance(address account); error ECDSAInvalidSignature(); @@ -299,6 +325,7 @@ interface IMockFBeanstalk { event CancelBlueprint(bytes32 blueprintHash); event ChangeUnderlying(address indexed token, int256 underlying); event Chop(address indexed account, address indexed token, uint256 amount, uint256 underlying); + event ClaimFertilizer(uint256[] ids, uint256 beans); event ClaimPlenty(address indexed account, address token, uint256 plenty); event Convert( address indexed account, @@ -1505,6 +1532,20 @@ interface IMockFBeanstalk { uint256 gasPriceBid ) external payable returns (uint256 ticketID); + function transmitOut( + address destination, + bytes[] calldata assets, + bytes calldata data + ) external; + + function transmitIn( + address user, + bytes[] calldata deposits, + bytes[] calldata plots, + bytes[] calldata fertilizer, + bytes calldata data + ) external; + function mintBeans(address to, uint256 amount) external; function mintFertilizer( @@ -1538,6 +1579,14 @@ interface IMockFBeanstalk { GerminationSide side ) external; + function mockInitializeGaugeForToken( + address token, + bytes4 gaugePointSelector, + bytes4 liquidityWeightSelector, + uint96 gaugePoints, + uint64 optimalPercentDepositedBdv + ) external; + function mockInitState() external; function mockLiquidityWeight() external pure returns (uint256); @@ -1813,6 +1862,8 @@ interface IMockFBeanstalk { function seasonTime() external view returns (uint64); + function seedGaugeSunSunrise(int256 deltaB, uint256 caseId) external; + function seedGaugeSunSunrise(int256 deltaB, uint256 caseId, bool oracleFailure) external; function setAbovePegE(bool peg) external; @@ -1851,6 +1902,10 @@ interface IMockFBeanstalk { function setMaxTempE(uint32 number) external; + function initDestinationField(uint256 srcInitPods) external; + + function setMerkleRootE(address unripeToken, bytes32 root) external; + function setNextSowTimeE(uint32 _time) external; function setPenaltyParams(uint256 recapitalized, uint256 fertilized) external; @@ -1953,12 +2008,16 @@ interface IMockFBeanstalk { function totalPods(uint256 fieldId) external view returns (uint256); + function totalProcessed(uint256 fieldId) external view returns (uint256); + function totalRainRoots() external view returns (uint256); function totalRealSoil() external view returns (uint256); function totalRoots() external view returns (uint256); + function totalSlashed(uint256 fieldId) external view returns (uint256); + function totalSoil() external view returns (uint256); function totalSoilAtMorningTemp( @@ -2166,4 +2225,15 @@ interface IMockFBeanstalk { function woohoo() external pure returns (uint256); function wrapEth(uint256 amount, uint8 mode) external payable; + + function getTokenPriceFromExternal( + address token, + uint256 lookback + ) external view returns (uint256 tokenPrice); + + function approveReciever(address owner, address reciever) external; + + function getReciever(address owner) external view returns (address); + + function setRecieverForL1Migration(address owner, address reciever) external; } diff --git a/protocol/contracts/interfaces/ITransmitInFacet.sol b/protocol/contracts/interfaces/ITransmitInFacet.sol new file mode 100644 index 0000000000..9ab38d79f8 --- /dev/null +++ b/protocol/contracts/interfaces/ITransmitInFacet.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +interface ITransmitInFacet { + function transmitIn( + address user, + bytes[][] calldata assets, + bytes calldata //data + ) external; +} diff --git a/protocol/contracts/interfaces/ITransmitOutFacet.sol b/protocol/contracts/interfaces/ITransmitOutFacet.sol new file mode 100644 index 0000000000..4a7ccf4de6 --- /dev/null +++ b/protocol/contracts/interfaces/ITransmitOutFacet.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {LibTransmitOut} from "contracts/libraries/ForkSystem/LibTransmitOut.sol"; + +interface ITransmitOutFacet { + function transmitOut( + address destination, + bytes[] calldata assets, + bytes calldata data + ) external; +} diff --git a/protocol/contracts/libraries/ForkSystem/LibTransmitIn.sol b/protocol/contracts/libraries/ForkSystem/LibTransmitIn.sol new file mode 100644 index 0000000000..d29335ba4d --- /dev/null +++ b/protocol/contracts/libraries/ForkSystem/LibTransmitIn.sol @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +import {C} from "contracts/C.sol"; +import {AppStorage} from "contracts/libraries/LibAppStorage.sol"; +import {LibConvert} from "contracts/libraries/Convert/LibConvert.sol"; +import {LibWellBdv} from "contracts/libraries/Well/LibWellBdv.sol"; +import {LibWell} from "contracts/libraries/Well/LibWell.sol"; +import {IWell} from "contracts/interfaces/basin/IWell.sol"; +import {LibFertilizer} from "contracts/libraries/LibFertilizer.sol"; +import {LibAppStorage} from "contracts/libraries/LibAppStorage.sol"; +import {LibWhitelistedTokens} from "contracts/libraries/Silo/LibWhitelistedTokens.sol"; +import {LibField} from "contracts/libraries/LibField.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; +import {IFertilizer} from "contracts/interfaces/IFertilizer.sol"; + +/** + * @title LibTransmitIn + * @author funderbrker + * @notice Library handling inbound migration and minting of protocol assets. + */ +library LibTransmitIn { + using SafeCast for uint256; + + event DepositTransmittedIn(address indexed user, SourceDeposit deposit); + + event FertilizerTransmittedIn(address indexed user, SourceFertilizer fertilizer); + + event PlotTransmittedIn(address indexed user, SourcePlot sourcePlot); + + // Definitions must match source migration definitions. May require multiple definitions. + struct SourceDeposit { + address token; + uint256 amount; + int96 stem; + uint256[] sourceMinTokenAmountsOut; // LP only + uint256 destMinLpOut; // LP only + uint256 lpDeadline; // LP only + uint256 _grownStalk; // not stalk // need to change logic + uint256 _burnedBeans; + address _transferredToken; // NOTE what if LP type is not supported at destination? + uint256 _transferredTokenAmount; + } + + struct SourcePlot { + uint256 fieldId; + uint256 index; + uint256 amount; + uint256 existingIndex; + } + + struct SourceFertilizer { + uint128 id; + uint256 amount; + uint128 _remainingBpf; + } + + /** + * @notice Mint and deposit new Beans and LP to the user, along with grown stalk. + * @dev Mints Bean and LP at 1:1 ratio with source. This assumption can be altered in children. + * @dev Underlying non-Bean tokens must already be transferred to this address. + */ + function transmitInDeposits(address user, bytes[] calldata deposits) internal { + if (deposits.length == 0) return; + + address[] memory whitelistedTokens = LibWhitelistedTokens.getWhitelistedWellLpTokens(); + for (uint256 i = 0; i < deposits.length; i++) { + SourceDeposit memory deposit = abi.decode(deposits[i], (SourceDeposit)); + + _alterDeposit(deposit); + + IBean(C.bean()).mint(address(this), deposit._burnedBeans); + + // If LP deposit. + if (deposit._transferredToken != address(0)) { + bool lpMatched; + // Look for corresponding whitelisted well. + for (uint j; j < whitelistedTokens.length; j++) { + address well = whitelistedTokens[j]; + if ( + address(LibWell.getNonBeanTokenFromWell(well)) != deposit._transferredToken + ) { + continue; + } + uint256[] memory tokenAmountsIn = new uint256[](2); + tokenAmountsIn[LibWell.getBeanIndexFromWell(well)] = deposit._burnedBeans; + tokenAmountsIn[LibWell.getNonBeanIndexFromWell(well)] = deposit + ._transferredTokenAmount; + + IERC20(deposit._transferredToken).approve( + well, + uint256(deposit._transferredTokenAmount) + ); + IBean(C.bean()).approve(well, deposit._burnedBeans); + uint256 lpAmount = IWell(whitelistedTokens[j]).addLiquidity( + tokenAmountsIn, + deposit.destMinLpOut, + address(this), + deposit.lpDeadline + ); + + LibConvert._depositTokensForConvert( + well, + lpAmount, // amount + LibWellBdv.bdv(well, lpAmount), // bdv + deposit._grownStalk, + user + ); + lpMatched = true; + break; + } + require(lpMatched, "LP not whitelisted"); + } + // else if Bean deposit. + else { + // Update Beanstalk state and mint Beans to user. Bypasses standard minting calcs. + LibConvert._depositTokensForConvert( + C.bean(), + deposit._burnedBeans, // amount + deposit._burnedBeans, // bdv + deposit._grownStalk, + user + ); + } + emit DepositTransmittedIn(user, deposit); + } + } + + /** + * @notice Mint equivalent fertilizer to the user such that they retain all remaining BPF. + * + * If there is a large gap in migrated plots then the line can be instantly pushed + * all the way to the end of the hole. So if only one plot migrates, line can + * immediately skip to that plot, even if it is at the end of the line. + * Could partially mitigate with a no-sunrise migration window or a time delay + * until inbound line can harvest. + */ + function transmitInFertilizer(address user, bytes[] memory fertilizer) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + if (fertilizer.length == 0) return; + for (uint256 i = 0; i < fertilizer.length; i++) { + SourceFertilizer memory sourceFert = abi.decode(fertilizer[i], (SourceFertilizer)); + + _alterFertilizer(sourceFert); + + // Update Beanstalk state and mint Fert to user. Bypasses standard minting calcs. + LibFertilizer.IncrementFertState(sourceFert.amount, sourceFert._remainingBpf); + IFertilizer(s.sys.tokens.fertilizer).beanstalkMint( + user, + s.sys.fert.bpf + sourceFert._remainingBpf, + sourceFert.amount.toUint128(), + s.sys.fert.bpf + ); + emit FertilizerTransmittedIn(user, sourceFert); + } + } + + /** + * @notice Create Plots in alternative Field and assign them to the migrating user. + * @dev Holes between transmitted-in Plots are filled with Slashed Plots. + * @dev Assumes that no sowing will occur in the same Field as inbound migrations. + * + * This contorted design is necessary to maintain constant time + * harvest operations on a pod line that may contain holes. Null plots represent + * holes in such a way that all pods can be accounted for and a plot can be acted + * on without any knowledge of other plots. + */ + function transmitInPlots(address user, bytes[] memory plots) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + if (plots.length == 0) return; + for (uint256 i; i < plots.length; i++) { + SourcePlot memory sourcePlot = abi.decode(plots[i], (SourcePlot)); + + _alterPlot(sourcePlot); + + require(sourcePlot.fieldId == 0, "Field unsupported"); + // If the plot has missed harvesting or it was sown after destination deployment, + // append the Plot to the end of the Pod line. + if ( + sourcePlot.index < s.sys.fields[C.DEST_FIELD].harvestable || + sourcePlot.index > s.sys.fields[C.DEST_FIELD].srcInitPods + ) { + _plotPush(user, sourcePlot.amount); + } else { + _insertInNullPlot( + user, + sourcePlot.index, + sourcePlot.amount, + sourcePlot.existingIndex + ); + } + emit PlotTransmittedIn(user, sourcePlot); + } + } + + /** + * @notice Insert a plot into an existing null plot. + * @dev Requires that the existing plot is owned by null. This is possible bc null plots are + * always injected into gaps. + */ + function _insertInNullPlot( + address user, + uint256 index, + uint256 amount, + uint256 existingIndex + ) private { + AppStorage storage s = LibAppStorage.diamondStorage(); + // prev plot is provided by user, but it is verified in insertion. + uint256 existingAmount = s.accts[address(0)].fields[C.DEST_FIELD].plots[existingIndex]; + uint256 endIndex = existingIndex + existingAmount; + require(existingAmount > 0, "existingIndex non null"); + require(existingIndex <= index, "existingIndex too large"); + uint256 followingIndex = index + amount; + require(endIndex >= followingIndex, "endIndex too small"); + uint256 followingAmount = endIndex - followingIndex; + + // Delete existing null plot. + LibField.deletePlot(address(0), C.DEST_FIELD, existingIndex); + + // Create preceding null plot, user plot, and following null plot. + LibField.createPlot(address(0), C.DEST_FIELD, existingIndex, index - existingIndex); + LibField.createPlot(user, C.DEST_FIELD, index, amount); + LibField.createPlot(address(0), C.DEST_FIELD, followingIndex, followingAmount); + if (followingIndex > s.sys.fields[C.DEST_FIELD].latestTransmittedPlotIndex) { + _updateLatestPlot(address(0), followingIndex, followingAmount); + } + } + + /** + * @notice Insert a plot after the last existing in the Field. Null or non-null. + */ + function _plotPush(address user, uint256 amount) private { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint256 index = s.sys.fields[C.DEST_FIELD].latestTransmittedPlotIndex + + s + .accts[s.sys.fields[C.DEST_FIELD].latestTransmittedPlotOwner] + .fields[C.DEST_FIELD] + .plots[s.sys.fields[C.DEST_FIELD].latestTransmittedPlotIndex]; + + // Create preceding null plot and user plot. + LibField.createPlot(user, C.DEST_FIELD, index, amount); + _updateLatestPlot(user, index, amount); + } + + function _updateLatestPlot(address user, uint256 index, uint256 amount) private { + AppStorage storage s = LibAppStorage.diamondStorage(); + s.sys.fields[C.DEST_FIELD].latestTransmittedPlotOwner = user; + s.sys.fields[C.DEST_FIELD].latestTransmittedPlotIndex = index; + s.sys.fields[C.DEST_FIELD].pods = index + amount; + } + + function _initDestinationField(uint256 srcInitPods) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + s.sys.fields[C.DEST_FIELD].srcInitPods = srcInitPods; + _plotPush(address(0), srcInitPods); + } + + function _alterDeposit(SourceDeposit memory) private { + // NOTE this is a placeholder for future child-specific logic. + return; + } + + function _alterFertilizer(SourceFertilizer memory) private { + // NOTE this is a placeholder for future child-specific logic. + return; + } + + function _alterPlot(SourcePlot memory) private { + // NOTE this is a placeholder for future child-specific logic. + return; + } +} diff --git a/protocol/contracts/libraries/ForkSystem/LibTransmitOut.sol b/protocol/contracts/libraries/ForkSystem/LibTransmitOut.sol new file mode 100644 index 0000000000..33ec2f57d6 --- /dev/null +++ b/protocol/contracts/libraries/ForkSystem/LibTransmitOut.sol @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {C} from "contracts/C.sol"; +import {AppStorage} from "contracts/libraries/LibAppStorage.sol"; +import {LibUnripe} from "contracts/libraries/LibUnripe.sol"; +import {LibSilo} from "contracts/libraries/Silo/LibSilo.sol"; +import {LibTokenSilo} from "contracts/libraries/Silo/LibTokenSilo.sol"; +import {LibWell} from "contracts/libraries/Well/LibWell.sol"; +import {IWell} from "contracts/interfaces/basin/IWell.sol"; +import {LibAppStorage} from "contracts/libraries/LibAppStorage.sol"; +import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol"; +import {LibFertilizer} from "contracts/libraries/LibFertilizer.sol"; +import {LibMarket} from "contracts/libraries/LibMarket.sol"; +import {LibField} from "contracts/libraries/LibField.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; +import {IFertilizer} from "contracts/interfaces/IFertilizer.sol"; +import {LibGerminate} from "contracts/libraries/Silo/LibGerminate.sol"; + +/** + * @title LibTransmitOut + * @author funderbrker + * @notice Library handling outbound migration and burning of protocol assets. + */ +library LibTransmitOut { + event DepositTransmittedOut(address indexed user, SourceDeposit deposit); + + event FertilizerTransmittedOut(address indexed user, SourceFertilizer fertilizer); + + event PlotTransmittedOut(address indexed user, SourcePlot sourcePlot); + + // Need to define structs locally to contain additional migration information. + struct SourceDeposit { + address token; + uint256 amount; + int96 stem; + uint256[] sourceMinTokenAmountsOut; // LP only + uint256 destMinLpOut; // LP only + uint256 lpDeadline; // LP only + uint256 _grownStalk; // not stalk // need to change logic + uint256 _burnedBeans; + address _transferredToken; // NOTE what if LP type is not supported at destination? + uint256 _transferredTokenAmount; + } + + struct SourcePlot { + uint256 fieldId; + uint256 index; + uint256 amount; + uint256 existingIndex; + } + + struct SourceFertilizer { + uint128 id; + uint256 amount; + uint128 _remainingBpf; + } + + // Current system does not offer a clean way to supply all deposits across all tokens. + // Could instead (for deposits + fert + pods) have the external function arguments all be arrays of structs + // where the structs are the locally defined definitions. But this is a little sloppy, bc the structs are + // partially populate during the execution. + // Could use two structs: a selector struct and a migration definition struct. Seems very bloated. + // Could mark fields that do not need to be populated with a "_" prefix <- this is a bit ugly, but i think best + + // TODO what to do with LP tokens? The destination does not want a token with the source Bean underlying it. Should unwind the LP, burn the bean, and send the non-Bean token. + // TODO swapping / exiting LP will be difficult to implement with a proper minimum out. + // remove in balanced ratio? with UI setting minimum out of each asset... + /** + * @notice Withdraw deposits and sends underlying ERC20 of asset. Burns Beans. + * @return depositsOut The set of deposits to transmit, encoded as bytes. + */ + function transmitOutDeposits( + address user, + address destination, + SourceDeposit[] memory deposits + ) internal returns (bytes[] memory depositsOut) { + if (deposits.length == 0) return depositsOut; + AppStorage storage s = LibAppStorage.diamondStorage(); + depositsOut = new bytes[](deposits.length); + + address[] memory tokensMown = new address[](s.sys.silo.whitelistStatuses.length); + for (uint256 i; i < deposits.length; i++) { + require(!LibUnripe.isUnripe(deposits[i].token), "Unripe not supported"); + + // Mow each migrating token once. + for (uint256 j; j < tokensMown.length; j++) { + if (tokensMown[j] == deposits[i].token) { + break; + } + if (tokensMown[j] == address(0)) { + tokensMown[j] = deposits[i].token; + LibSilo._mow(user, deposits[i].token); + break; + } + } + + // if the deposit is germinating, it cannot be transmitted + if (LibGerminate.isGerminating(deposits[i].token, deposits[i].stem)) { + revert("Transmit: Cannot transmit germinating deposits"); + } + + // Withdraw deposit from Silo. + (, deposits[i]._grownStalk, , ) = LibSilo._withdrawDeposit( + user, + deposits[i].token, + deposits[i].stem, + deposits[i].amount + ); + + if (deposits[i].token == s.sys.tokens.bean) { + // Burn Bean. + deposits[i]._burnedBeans = deposits[i].amount; + IBean(s.sys.tokens.bean).burn(deposits[i].amount); + } + // If Well LP token. Only supports Wells with Bean:Token. + else if (LibWell.isWell(deposits[i].token)) { + // Withdraw LP token. + uint256[] memory tokenAmountsOut = IWell(deposits[i].token).removeLiquidity( + deposits[i].amount, + deposits[i].sourceMinTokenAmountsOut, + address(this), + deposits[i].lpDeadline + ); + + // Burn Bean. + deposits[i]._burnedBeans = tokenAmountsOut[ + LibWell.getBeanIndexFromWell(deposits[i].token) + ]; + IBean(s.sys.tokens.bean).burn(deposits[i]._burnedBeans); + + // Send non-Bean token. + IERC20 nonBeanToken = LibWell.getNonBeanTokenFromWell(deposits[i].token); + uint256 tokenAmount = tokenAmountsOut[ + LibWell.getNonBeanIndexFromWell(deposits[i].token) + ]; + LibTransfer.sendToken( + nonBeanToken, + tokenAmount, + destination, + LibTransfer.To.EXTERNAL + ); + deposits[i]._transferredToken = address(nonBeanToken); + deposits[i]._transferredTokenAmount = tokenAmount; + } else { + // Must be Bean or a whitelisted Well token. + revert("Invalid token"); + } + + depositsOut[i] = abi.encode(deposits[i]); + emit DepositTransmittedOut(user, deposits[i]); + } + } + + /** + * @notice Slashes plots, which can later be burned. Populates and encodes migration data. + * @return plotsOut The plots to transmit, encoded as bytes. + * @dev Slashes the Pods by setting the owner to 0x0. They remain in Pod line until they + * become harvestable and can be Burned. + */ + function transmitOutPlots( + address account, + SourcePlot[] memory plots + ) internal returns (bytes[] memory plotsOut) { + if (plots.length == 0) return plotsOut; + AppStorage storage s = LibAppStorage.diamondStorage(); + plotsOut = new bytes[](plots.length); + for (uint256 i; i < plots.length; i++) { + SourcePlot memory plot = plots[i]; + require(plot.index >= s.sys.fields[plot.fieldId].harvestable, "Already harvestable"); + uint256 pods = s.accts[account].fields[plot.fieldId].plots[plot.index]; + require(plot.amount > 0, "No Pods to transmit"); + require(pods >= plot.amount, "Insufficient Pods"); + + // Remove Plots from user. + LibMarket._cancelPodListing(account, plot.fieldId, plot.index); + LibField.deletePlot(account, plot.fieldId, plot.index); + + // Add new plots to null address. + LibField.createPlot(address(0), plot.fieldId, plot.index, plot.amount); + + // Add partial plots back to user. + uint256 remainingPods = pods - plot.amount; + if (remainingPods > 0) { + uint256 newIndex = plot.index + plot.amount; + s.accts[account].fields[s.sys.activeField].plots[newIndex] = remainingPods; + s.accts[account].fields[s.sys.activeField].plotIndexes.push(newIndex); + } + + plotsOut[i] = abi.encode(plot); + emit PlotTransmittedOut(account, plot); + } + } + + /** + * @notice Burns Fertilizer. Populates and encodes migration data. + * @return fertilizerOut The Fertilizer to transmit, encoded as bytes. + */ + function transmitOutFertilizer( + address account, + SourceFertilizer[] memory fertilizer + ) internal returns (bytes[] memory fertilizerOut) { + if (fertilizer.length == 0) return fertilizerOut; + AppStorage storage s = LibAppStorage.diamondStorage(); + fertilizerOut = new bytes[](fertilizer.length); + + /* + 0. Update user. + 1. Decrement each fert individually. + 2. Check leftoverBeans. + */ + uint256[] memory ids = new uint256[](fertilizer.length); + for (uint256 i; i < fertilizer.length; i++) { + ids[i] = fertilizer[i].id; + + // Do not allow duplicate fertilizer IDs. + for (uint256 j; j < i; j++) { + require(ids[j] != fertilizer[i].id, "Duplicate Fertilizer ID"); + } + + uint128 remainingBpf = fertilizer[i].id - s.sys.fert.bpf; + IFertilizer(s.sys.tokens.fertilizer).beanstalkBurn( + account, + s.sys.fert.bpf + remainingBpf, + uint128(fertilizer[i].amount), + s.sys.fert.bpf + ); + LibFertilizer.decrementFertState(fertilizer[i].amount, remainingBpf); + + fertilizer[i]._remainingBpf = remainingBpf; + fertilizerOut[i] = abi.encode(fertilizer[i]); + emit FertilizerTransmittedOut(account, fertilizer[i]); + } + + // If leftover beans are greater than obligations, drop excess leftovers. Rounding loss. + uint256 unfertilizedBeans = s.sys.fert.unfertilizedIndex - s.sys.fert.fertilizedIndex; + if (unfertilizedBeans < s.sys.fert.leftoverBeans) { + s.sys.fert.leftoverBeans = 0; + } + } +} diff --git a/protocol/contracts/libraries/LibDibbler.sol b/protocol/contracts/libraries/LibDibbler.sol index 5da2d217ca..5c0965db11 100644 --- a/protocol/contracts/libraries/LibDibbler.sol +++ b/protocol/contracts/libraries/LibDibbler.sol @@ -7,10 +7,11 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {PRBMath} from "@prb/math/contracts/PRBMath.sol"; import {LibPRBMathRoundable} from "contracts/libraries/LibPRBMathRoundable.sol"; import {LibAppStorage, AppStorage} from "./LibAppStorage.sol"; -import {Account, Field} from "contracts/beanstalk/storage/Account.sol"; +import {Account} from "contracts/beanstalk/storage/Account.sol"; import {LibRedundantMath128} from "./LibRedundantMath128.sol"; import {LibRedundantMath32} from "./LibRedundantMath32.sol"; import {LibRedundantMath256} from "contracts/libraries/LibRedundantMath256.sol"; +import {LibField} from "contracts/libraries/LibField.sol"; import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol"; import {LibTractor} from "contracts/libraries/LibTractor.sol"; import {IBean} from "contracts/interfaces/IBean.sol"; @@ -140,11 +141,7 @@ library LibDibbler { uint256 index = s.sys.fields[activeField].pods; - s.accts[account].fields[activeField].plots[index] = pods; - s.accts[account].fields[activeField].plotIndexes.push(index); - s.accts[account].fields[activeField].piIndex[index] = - s.accts[account].fields[activeField].plotIndexes.length - - 1; + LibField.createPlot(account, activeField, index, pods); emit Sow(account, activeField, index, beans, pods); s.sys.fields[activeField].pods += pods; @@ -467,33 +464,4 @@ library LibDibbler { ); } } - - /** - * @notice removes a plot index from an accounts plotIndex list. - */ - function removePlotIndexFromAccount( - address account, - uint256 fieldId, - uint256 plotIndex - ) internal { - AppStorage storage s = LibAppStorage.diamondStorage(); - uint256 i = findPlotIndexForAccount(account, fieldId, plotIndex); - Field storage field = s.accts[account].fields[fieldId]; - field.plotIndexes[i] = field.plotIndexes[field.plotIndexes.length - 1]; - field.piIndex[field.plotIndexes[i]] = i; - field.piIndex[plotIndex] = type(uint256).max; - field.plotIndexes.pop(); - } - - /** - * @notice finds the index of a plot in an accounts plotIndex list. - */ - function findPlotIndexForAccount( - address account, - uint256 fieldId, - uint256 plotIndex - ) internal view returns (uint256 i) { - AppStorage storage s = LibAppStorage.diamondStorage(); - return s.accts[account].fields[fieldId].piIndex[plotIndex]; - } } diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 898856301b..37bbf47bce 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -18,7 +18,9 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {LibWell} from "contracts/libraries/Well/LibWell.sol"; import {LibUsdOracle} from "contracts/libraries/Oracle/LibUsdOracle.sol"; import {LibTractor} from "contracts/libraries/LibTractor.sol"; +import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol"; import {BeanstalkERC20} from "contracts/tokens/ERC20/BeanstalkERC20.sol"; +import {IFertilizer} from "contracts/interfaces/IFertilizer.sol"; /** * @author Publius @@ -56,25 +58,56 @@ library LibFertilizer { uint256 fertilizerAmount, uint256 minLP ) internal returns (uint128 id) { - AppStorage storage s = LibAppStorage.diamondStorage(); - - uint128 fertilizerAmount128 = fertilizerAmount.toUint128(); - // Calculate Beans Per Fertilizer and add to total owed uint128 bpf = getBpf(season); - s.sys.fert.unfertilizedIndex = s.sys.fert.unfertilizedIndex.add(fertilizerAmount.mul(bpf)); - // Get id - id = s.sys.fert.bpf.add(bpf); - // Update Total and Season supply - s.sys.fert.fertilizer[id] = s.sys.fert.fertilizer[id].add(fertilizerAmount128); - s.sys.fert.activeFertilizer = s.sys.fert.activeFertilizer.add(fertilizerAmount); + id = IncrementFertState(fertilizerAmount, bpf); // Add underlying to Unripe Beans and Unripe LP addUnderlying(tokenAmountIn, fertilizerAmount.mul(DECIMALS), minLP); + } + + function IncrementFertState( + uint256 fertilizerAmount, + uint128 remainingBpf + ) internal returns (uint128 id) { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint128 fertilizerAmount128 = fertilizerAmount.toUint128(); + s.sys.fert.unfertilizedIndex += fertilizerAmount * remainingBpf; + id = s.sys.fert.bpf + remainingBpf; + s.sys.fert.fertilizer[id] += fertilizerAmount128; + s.sys.fert.activeFertilizer += fertilizerAmount; + // If not first time adding Fertilizer with this id, return if (s.sys.fert.fertilizer[id] > fertilizerAmount128) return id; // If first time, log end Beans Per Fertilizer and add to Season queue. push(id); - emit SetFertilizer(id, bpf); + emit SetFertilizer(id, remainingBpf); + return id; + } + + function decrementFertState( + uint256 fertilizerAmount, + uint128 remainingBpf + ) internal returns (uint128 id) { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint128 fertilizerAmount128 = fertilizerAmount.toUint128(); + + s.sys.fert.unfertilizedIndex -= fertilizerAmount * remainingBpf; + id = s.sys.fert.bpf + remainingBpf; + s.sys.fert.fertilizer[id] -= fertilizerAmount128; + s.sys.fert.activeFertilizer -= fertilizerAmount; + + return id; + } + + function claimFertilized(uint256[] memory ids, LibTransfer.To mode) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint256 amount = IFertilizer(s.sys.tokens.fertilizer).beanstalkUpdate( + LibTractor._user(), + ids, + s.sys.fert.bpf + ); + s.sys.fert.fertilizedPaidIndex += amount; + LibTransfer.sendToken(IERC20(s.sys.tokens.bean), amount, LibTractor._user(), mode); } /** diff --git a/protocol/contracts/libraries/LibField.sol b/protocol/contracts/libraries/LibField.sol new file mode 100644 index 0000000000..949bbf2bf9 --- /dev/null +++ b/protocol/contracts/libraries/LibField.sol @@ -0,0 +1,76 @@ +/* + SPDX-License-Identifier: MIT +*/ + +pragma solidity ^0.8.20; + +import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; +import {Field} from "contracts/beanstalk/storage/Account.sol"; + +/** + * @author funderbrker + * @title LibField + **/ + +library LibField { + /** + * @dev Does not check for existence of index in plotIndexes. + * @dev Plot indexes not tracked for null address. + */ + function createPlot(address account, uint256 fieldId, uint256 index, uint256 amount) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + if (amount == 0) return; + s.accts[account].fields[fieldId].plots[index] = amount; + if (account != address(0)) { + s.accts[account].fields[fieldId].plotIndexes.push(index); + s.accts[account].fields[fieldId].piIndex[index] = + s.accts[account].fields[fieldId].plotIndexes.length - + 1; + } + } + + /** + * @dev Plot indexes are not tracked for null address. + */ + function deletePlot(address account, uint256 fieldId, uint256 index) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + delete s.accts[account].fields[fieldId].plots[index]; + if (account != address(0)) removePlotIndexFromAccount(account, fieldId, index); + } + + /** + * @notice removes a plot index from an accounts plotIndex list. + */ + function removePlotIndexFromAccount( + address account, + uint256 fieldId, + uint256 plotIndex + ) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint256 i = findPlotIndexForAccount(account, fieldId, plotIndex); + Field storage field = s.accts[account].fields[fieldId]; + field.plotIndexes[i] = field.plotIndexes[field.plotIndexes.length - 1]; + field.plotIndexes.pop(); + } + + /** + * @notice finds the index of a plot in an accounts plotIndex list. + */ + function findPlotIndexForAccount( + address account, + uint256 fieldId, + uint256 plotIndex + ) internal view returns (uint256 i) { + AppStorage storage s = LibAppStorage.diamondStorage(); + Field storage field = s.accts[account].fields[fieldId]; + uint256[] memory plotIndexes = field.plotIndexes; + uint256 length = plotIndexes.length; + while (plotIndexes[i] != plotIndex) { + i++; + if (i >= length) { + revert("Id not found"); + } + } + return i; + } +} diff --git a/protocol/contracts/libraries/Silo/LibGerminate.sol b/protocol/contracts/libraries/Silo/LibGerminate.sol index 088008dc45..f9861d1caa 100644 --- a/protocol/contracts/libraries/Silo/LibGerminate.sol +++ b/protocol/contracts/libraries/Silo/LibGerminate.sol @@ -395,6 +395,14 @@ library LibGerminate { ); } + function isGerminating(address token, int96 stem) internal view returns (bool) { + (GerminationSide germinationState, ) = getGerminationState(token, stem); + if (germinationState == GerminationSide.NOT_GERMINATING) { + return false; + } + return true; + } + /** * @notice determines whether a deposit (token + stem) should be germinating or not. * If germinating, determines whether the deposit should be set to even or odd. diff --git a/protocol/contracts/libraries/Silo/LibSilo.sol b/protocol/contracts/libraries/Silo/LibSilo.sol index e3e9e30401..183092b4c6 100644 --- a/protocol/contracts/libraries/Silo/LibSilo.sol +++ b/protocol/contracts/libraries/Silo/LibSilo.sol @@ -156,6 +156,187 @@ library LibSilo { uint256[] values ); + //////////////////////// WITHDRAW //////////////////////// + + //////////////////////// WITHDRAW //////////////////////// + + /** + * @notice Handles withdraw accounting. + * + * - {LibSilo._removeDepositFromAccount} calculates the stalk + * assoicated with a given deposit, and removes the deposit from the account. + * emits `RemoveDeposit` and `TransferSingle` events. + * + * - {_withdraw} updates the total value deposited in the silo, and burns + * the stalk assoicated with the deposits. + * + */ + function _withdrawDeposit( + address account, + address token, + int96 stem, + uint256 amount + ) + internal + returns ( + uint256 initialStalkRemoved, + uint256 grownStalkRemoved, + uint256 bdvRemoved, + GerminationSide side + ) + { + // Remove the Deposit from `account`. + (initialStalkRemoved, grownStalkRemoved, bdvRemoved, side) = _removeDepositFromAccount( + account, + token, + stem, + amount, + LibTokenSilo.Transfer.emitTransferSingle + ); + if (side == GerminationSide.NOT_GERMINATING) { + // remove the deposit from totals + _withdraw( + account, + token, + amount, + bdvRemoved, + initialStalkRemoved.add(grownStalkRemoved) + ); + } else { + // remove deposit from germination, and burn the grown stalk. + // grown stalk does not germinate and is not counted in germinating totals. + _withdrawGerminating(account, token, amount, bdvRemoved, initialStalkRemoved, side); + + if (grownStalkRemoved > 0) { + burnActiveStalk(account, grownStalkRemoved); + } + } + } + + /** + * @notice Handles withdraw accounting for multiple deposits. + * + * - {LibSilo._removeDepositsFromAccount} removes the deposits from the account, + * and returns the total tokens, stalk, and bdv removed from the account. + * + * - {_withdraw} updates the total value deposited in the silo, and burns + * the stalk assoicated with the deposits. + * + */ + function _withdrawDeposits( + address account, + address token, + int96[] calldata stems, + uint256[] calldata amounts + ) internal returns (uint256) { + require(stems.length == amounts.length, "Silo: Crates, amounts are diff lengths."); + + LibSilo.AssetsRemoved memory ar = LibSilo._removeDepositsFromAccount( + account, + token, + stems, + amounts, + LibSilo.ERC1155Event.EMIT_BATCH_EVENT + ); + + // withdraw deposits that are not germinating. + if (ar.active.tokens > 0) { + _withdraw(account, token, ar.active.tokens, ar.active.bdv, ar.active.stalk); + } + + // withdraw Germinating deposits from odd seasons + if (ar.odd.tokens > 0) { + _withdrawGerminating( + account, + token, + ar.odd.tokens, + ar.odd.bdv, + ar.odd.stalk, + GerminationSide.ODD + ); + } + + // withdraw Germinating deposits from even seasons + if (ar.even.tokens > 0) { + _withdrawGerminating( + account, + token, + ar.even.tokens, + ar.even.bdv, + ar.even.stalk, + GerminationSide.EVEN + ); + } + + if (ar.grownStalkFromGermDeposits > 0) { + LibSilo.burnActiveStalk(account, ar.grownStalkFromGermDeposits); + } + + // we return the summation of all tokens removed from the silo. + // to be used in {SiloFacet.withdrawDeposits}. + return ar.active.tokens.add(ar.odd.tokens).add(ar.even.tokens); + } + + /** + * @dev internal helper function for withdraw accounting. + */ + function _withdraw( + address account, + address token, + uint256 amount, + uint256 bdv, + uint256 stalk + ) private { + // Decrement total deposited in the silo. + LibTokenSilo.decrementTotalDeposited(token, amount, bdv); + // Burn stalk and roots associated with the stalk. + LibSilo.burnActiveStalk(account, stalk); + } + + /** + * @dev internal helper function for withdraw accounting with germination. + * @param side determines whether to withdraw from odd or even germination. + */ + function _withdrawGerminating( + address account, + address token, + uint256 amount, + uint256 bdv, + uint256 stalk, + GerminationSide side + ) private { + AppStorage storage s = LibAppStorage.diamondStorage(); + // Deposited Earned Beans do not germinate. Thus, when withdrawing a Bean Deposit + // with a Germinating Stem, Beanstalk needs to determine how many of the Beans + // were Planted vs Deposited from a Circulating/Farm balance. + // If a Farmer's Germinating Stalk for a given Season is less than the number of + // Deposited Beans in that Season, then it is assumed that the excess Beans were + // Planted. + if (token == s.sys.tokens.bean) { + (uint256 germinatingStalk, uint256 earnedBeansStalk) = LibSilo.checkForEarnedBeans( + account, + stalk, + side + ); + // set the bdv and amount accordingly to the stalk. + stalk = germinatingStalk; + uint256 earnedBeans = earnedBeansStalk.div(C.STALK_PER_BEAN); + amount = amount - earnedBeans; + // note: the 1 Bean = 1 BDV assumption cannot be made here for input `bdv`, + // as a user converting a germinating LP deposit into bean may have an inflated bdv. + // thus, amount and bdv are decremented by the earnedBeans (where the 1 Bean = 1 BDV assumption can be made). + bdv = bdv - earnedBeans; + + // burn the earned bean stalk (which is active). + LibSilo.burnActiveStalk(account, earnedBeansStalk); + // calculate earnedBeans and decrement totalDeposited. + LibTokenSilo.decrementTotalDeposited(s.sys.tokens.bean, earnedBeans, earnedBeans); + } + // Decrement from total germinating. + LibTokenSilo.decrementTotalGerminating(token, amount, bdv, side); // Decrement total Germinating in the silo. + burnGerminatingStalk(account, uint128(stalk), side); // Burn stalk and roots associated with the stalk. + } + //////////////////////// MINT //////////////////////// /** @@ -271,7 +452,7 @@ library LibSilo { * @notice Burns germinating stalk. * @dev Germinating stalk does not have any roots assoicated with it. */ - function burnGerminatingStalk(address account, uint128 stalk, GerminationSide side) external { + function burnGerminatingStalk(address account, uint128 stalk, GerminationSide side) public { AppStorage storage s = LibAppStorage.diamondStorage(); s.accts[account].germinatingStalk[side] -= stalk; diff --git a/protocol/contracts/libraries/Well/LibWell.sol b/protocol/contracts/libraries/Well/LibWell.sol index 5973f2b1ea..b50e208293 100644 --- a/protocol/contracts/libraries/Well/LibWell.sol +++ b/protocol/contracts/libraries/Well/LibWell.sol @@ -122,6 +122,14 @@ library LibWell { beanIndex = getBeanIndex(tokens); } + /** + * @dev Returns the index of the first ERC20 given a Well. + */ + function getNonBeanIndexFromWell(address well) internal view returns (uint beanIndex) { + IERC20[] memory tokens = IWell(well).tokens(); + beanIndex = getNonBeanIndex(tokens); + } + function getNonBeanTokenFromWell(address well) internal view returns (IERC20 nonBeanToken) { IERC20[] memory tokens = IWell(well).tokens(); return tokens[getNonBeanIndex(tokens)]; diff --git a/protocol/contracts/mocks/MockFertilizer.sol b/protocol/contracts/mocks/MockFertilizer.sol index 10977e5a4f..9db3c3333a 100644 --- a/protocol/contracts/mocks/MockFertilizer.sol +++ b/protocol/contracts/mocks/MockFertilizer.sol @@ -14,4 +14,39 @@ contract MockFertilizer is Fertilizer { function initialize() public initializer { __Internallize_init(""); } + + /** + * @dev No access control for testing. + */ + function beanstalkUpdate( + address account, + uint256[] memory ids, + uint128 bpf + ) external override returns (uint256) { + return __update(account, ids, uint256(bpf)); + } + + /** + * @dev No access control for testing. + */ + function beanstalkMint( + address account, + uint256 id, + uint128 amount, + uint128 bpf + ) external override { + _beanstalkMint(account, id, amount, bpf); + } + + /** + * @dev No access control for testing. + */ + function beanstalkBurn( + address account, + uint256 id, + uint128 amount, + uint128 bpf + ) external override { + _beanstalkBurn(account, id, amount, bpf); + } } diff --git a/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol b/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol index a0d5a4a1a4..14f0ed71e7 100644 --- a/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol @@ -44,7 +44,7 @@ contract MockConvertFacet is ConvertFacet { uint256 bdv, uint256 grownStalk // address account ) external { - LibSilo._mow(msg.sender, token); + LibSilo._mow(LibTractor._user(), token); // if (account == address(0)) account = msg.sender; LibConvert._depositTokensForConvert(token, amount, bdv, grownStalk, LibTractor._user()); } diff --git a/protocol/contracts/mocks/mockFacets/MockFieldFacet.sol b/protocol/contracts/mocks/mockFacets/MockFieldFacet.sol index 87458b3dcd..f88c6ad426 100644 --- a/protocol/contracts/mocks/mockFacets/MockFieldFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockFieldFacet.sol @@ -8,6 +8,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {LibPRBMathRoundable} from "contracts/libraries/LibPRBMathRoundable.sol"; import "contracts/libraries/LibRedundantMath256.sol"; import "contracts/beanstalk/field/FieldFacet.sol"; +import {LibTransmitIn} from "contracts/libraries/ForkSystem/LibTransmitIn.sol"; /** * @author Publius, Brean @@ -214,4 +215,8 @@ contract MockFieldFacet is FieldFacet { function setMaxTemp(uint32 t) external { s.sys.weather.temp = t; } + + function initDestinationField(uint256 srcInitPods) external { + LibTransmitIn._initDestinationField(srcInitPods); + } } diff --git a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol index d850f7cee0..bb59502425 100644 --- a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol @@ -272,7 +272,7 @@ contract MockSeasonFacet is SeasonFacet { function resetState() public { for (uint256 i; i < s.sys.fieldCount; i++) { s.sys.fields[i].pods = 0; - s.sys.fields[i].harvested = 0; + s.sys.fields[i].processed = 0; s.sys.fields[i].harvestable = 0; } delete s.sys.silo; @@ -507,7 +507,7 @@ contract MockSeasonFacet is SeasonFacet { return currentGaugePoints; } - function mockinitializeGaugeForToken( + function mockInitializeGaugeForToken( address token, bytes4 gaugePointSelector, bytes4 liquidityWeightSelector, @@ -584,7 +584,7 @@ contract MockSeasonFacet is SeasonFacet { * @dev 0 = below peg, 1 = above peg, 2 = significantly above peg. */ function setPrice(uint256 price, address targetWell) public returns (int256 deltaB) { - // initalize beanTknPrice, and reserves. + // initialize beanTknPrice, and reserves. uint256 ethPrice = 1000e6; s.sys.usdTokenPrice[targetWell] = 1e24 / ethPrice; uint256[] memory reserves = IWell(targetWell).getReserves(); diff --git a/protocol/contracts/mocks/newMockInitDiamond.sol b/protocol/contracts/mocks/newMockInitDiamond.sol index d5cd721544..b96d05945c 100644 --- a/protocol/contracts/mocks/newMockInitDiamond.sol +++ b/protocol/contracts/mocks/newMockInitDiamond.sol @@ -6,11 +6,14 @@ pragma solidity ^0.8.20; import {AppStorage} from "contracts/beanstalk/storage/AppStorage.sol"; import {AssetSettings} from "contracts/beanstalk/storage/System.sol"; -import "contracts/beanstalk/init/InitalizeDiamond.sol"; +import "contracts/beanstalk/init/InitializeDiamond.sol"; import {LibWhitelistedTokens} from "contracts/libraries/Silo/LibWhitelistedTokens.sol"; import {LibWhitelist} from "contracts/libraries/Silo/LibWhitelist.sol"; import {LibUnripe} from "contracts/libraries/LibUnripe.sol"; +import {LibField} from "contracts/libraries/LibField.sol"; +import {LibTransmitIn} from "contracts/libraries/ForkSystem/LibTransmitIn.sol"; import {BDVFacet} from "contracts/beanstalk/silo/BDVFacet.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; /** * @author Publius, Brean @@ -21,7 +24,7 @@ import {BDVFacet} from "contracts/beanstalk/silo/BDVFacet.sol"; * - Whitelists the bean:wsteth well. * - Whitelists unripe assets. **/ -contract MockInitDiamond is InitalizeDiamond { +contract MockInitDiamond is InitializeDiamond { // min 1micro stalk earned per season due to germination. uint32 internal constant INIT_UR_BEAN_STALK_EARNED_PER_SEASON = 1; uint32 internal constant INIT_BEAN_WSTETH_WELL_STALK_EARNED_PER_SEASON = 4e6; @@ -38,17 +41,24 @@ contract MockInitDiamond is InitalizeDiamond { address internal constant UNRIPE_LP = 0x1BEA3CcD22F4EBd3d37d731BA31Eeca95713716D; function init() external { - // initalize the default state of the diamond. - // {see. InitalizeDiamond.initalizeDiamond()} - initalizeDiamond(BEAN, BEAN_ETH_WELL); + // initialize the default state of the diamond. + // {see. InitializeDiamond.initializeDiamond()} + initializeDiamond(BEAN, BEAN_ETH_WELL); - // initalizes unripe assets. + // initializes unripe assets. // sets the underlying LP token of unripeLP to the Bean:wstETH well. - whitelistUnderlyingUrLPWell(BEAN_WSTETH_WELL); - initalizeUnripeAssets(BEAN_WSTETH_WELL); + address underlyingUrLPWell = LibConstant.BEAN_WSTETH_WELL; + whitelistUnderlyingUrLPWell(underlyingUrLPWell); + initializeUnripeAssets(underlyingUrLPWell); + + // Set accepted source (ie parent) to be original Beanstalk. + s.sys.supportedSourceForks[0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5] = true; + // TODO: Set this based on configured source. + uint256 srcInitPods = 0; + LibTransmitIn._initDestinationField(srcInitPods); } - function initalizeUnripeAssets(address well) internal { + function initializeUnripeAssets(address well) internal { ( address[] memory unripeTokens, address[] memory underlyingTokens @@ -110,7 +120,7 @@ contract MockInitDiamond is InitalizeDiamond { } /** - * @notice initalizes the unripe silo settings. + * @notice initializes the unripe silo settings. * @dev unripe bean and unrpe lp has the same settings, * other than the BDV calculation. */ diff --git a/protocol/contracts/tokens/Fertilizer/Fertilizer.sol b/protocol/contracts/tokens/Fertilizer/Fertilizer.sol index 942a097b44..0976241bab 100644 --- a/protocol/contracts/tokens/Fertilizer/Fertilizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Fertilizer.sol @@ -40,7 +40,7 @@ contract Fertilizer is Internalizer { address account, uint256[] memory ids, uint128 bpf - ) external onlyOwner returns (uint256) { + ) external virtual onlyOwner returns (uint256) { return __update(account, ids, uint256(bpf)); } @@ -57,14 +57,17 @@ contract Fertilizer is Internalizer { uint256 id, uint128 amount, uint128 bpf - ) external onlyOwner { - if (_balances[id][account].amount > 0) { - uint256[] memory ids = new uint256[](1); - ids[0] = id; - _update(account, ids, bpf); - } - _balances[id][account].lastBpf = bpf; - _safeMint(account, id, amount, bytes("0")); + ) external virtual onlyOwner { + _beanstalkMint(account, id, amount, bpf); + } + + function beanstalkBurn( + address account, + uint256 id, + uint128 amount, + uint128 bpf + ) external virtual onlyOwner { + _beanstalkBurn(account, id, amount, bpf); } /** @@ -122,6 +125,25 @@ contract Fertilizer is Internalizer { emit ClaimFertilizer(ids, beans); } + function _beanstalkMint(address account, uint256 id, uint128 amount, uint128 bpf) internal { + if (_balances[id][account].amount > 0) { + uint256[] memory ids = new uint256[](1); + ids[0] = id; + _update(account, ids, bpf); + } + _balances[id][account].lastBpf = bpf; + _safeMint(account, id, amount, bytes("0")); + } + + function _beanstalkBurn(address account, uint256 id, uint128 amount, uint128 bpf) internal { + require(_balances[id][account].amount >= amount); + uint256[] memory ids = new uint256[](1); + ids[0] = id; + _update(account, ids, bpf); + _balances[id][account].lastBpf = bpf; + _safeBurn(account, id, amount, bytes("0")); + } + /** * @notice Returns the balance of fertilized beans of a fertilizer owner given a set of fertilizer ids diff --git a/protocol/contracts/tokens/Fertilizer/Fertilizer1155.sol b/protocol/contracts/tokens/Fertilizer/Fertilizer1155.sol index 2953cc2121..e5c303e705 100644 --- a/protocol/contracts/tokens/Fertilizer/Fertilizer1155.sol +++ b/protocol/contracts/tokens/Fertilizer/Fertilizer1155.sol @@ -88,6 +88,23 @@ contract Fertilizer1155 is ERC1155Upgradeable { __doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); } + function _safeBurn( + address from, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = _msgSender(); + + _transfer(from, address(0), id, amount); + + emit TransferSingle(operator, from, address(0), id, amount); + + __doSafeTransferAcceptanceCheck(operator, from, address(0), id, amount, data); + } + // The 3 functions below are copied from: // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/ERC1155.sol) // as they are private functions. diff --git a/protocol/foundry.toml b/protocol/foundry.toml index 2c463e9846..3fcd9a5691 100644 --- a/protocol/foundry.toml +++ b/protocol/foundry.toml @@ -42,7 +42,7 @@ ignored_warnings_from = [ gas_reports = ['*'] # Cache to `$HOME/.foundry/cache//`. no_storage_caching = false -no_match_contract = "Reseed" +no_match_contract = "Reseed|L1ReceiverFacetForkTest|L1ReceiverFacetTest" [profile.differential] match_test = "testDiff" diff --git a/protocol/scripts/deploy.js b/protocol/scripts/deploy.js index 1e5e77e86c..99d72c2d0d 100644 --- a/protocol/scripts/deploy.js +++ b/protocol/scripts/deploy.js @@ -199,9 +199,9 @@ async function addUnderlyingToUnripe(address = undefined, contract = BEANSTALK, * - token: the token to use for the well. * - basinComponents: the basin components that the well will use. * - whitelist: if true, whitelists the well to beanstalk. - * - siloSettings: if whitelist is true, initalizes seed values. + * - siloSettings: if whitelist is true, initializes seed values. */ -async function deployAndInitalizeMockBeanWell( +async function deployAndInitializeMockBeanWell( wellAddress = undefined, token = undefined, wellComponents = undefined, @@ -231,7 +231,7 @@ async function deployAndInitalizeMockBeanWell( let symbol = "BEAN" + (await tokenContract.symbol()) + (await wellComponents.wellFunction.symbol()) + "w"; - // initalize instanteous reserves. + // initialize instanteous reserves. await wellComponents.pump.setInstantaneousReserves(pumpBalances); await well.setSymbol(symbol); diff --git a/protocol/test/foundry/Invariable.t.sol b/protocol/test/foundry/Invariable.t.sol index 0d1994b11b..7af7a8a89f 100644 --- a/protocol/test/foundry/Invariable.t.sol +++ b/protocol/test/foundry/Invariable.t.sol @@ -17,23 +17,23 @@ contract InvariableTest is TestHelper { address[] siloUsers = new address[](3); function setUp() public { - initializeBeanstalkTestState(true, true); - MockToken(WETH).mint(BEANSTALK, 100_000); + initializeBeanstalkTestState(true, false, true); + MockToken(WETH).mint(address(bs), 100_000); siloUsers = createUsers(3); initializeUnripeTokens(siloUsers[0], 100e6, 100e18); mintTokensToUsers(siloUsers, BEAN, 100_000e6); - setUpSiloDepositTest(10_000e6, siloUsers); + setUpSiloDeposits(10_000e6, siloUsers); addFertilizerBasedOnSprouts(0, 100e6); - sowAmountForFarmer(siloUsers[0], 1_000e6); + sowForUser(siloUsers[0], 1_000e6); } /** * @notice Violates fundsSafu invariant through manipulation of internal and external assets. */ function test_fundsSafu(uint256 theftAmount) public { - theftAmount = bound(theftAmount, 1, bean.balanceOf(BEANSTALK)); + theftAmount = bound(theftAmount, 1, bean.balanceOf(address(bs))); // Advanced farm call containing one exploit call. Will be checked against fundsSafu. IMockFBeanstalk.AdvancedFarmCall[] diff --git a/protocol/test/foundry/Migration/L1Receiver.t.sol b/protocol/test/foundry/Migration/L1Receiver.t.sol index e954c7731f..998898b966 100644 --- a/protocol/test/foundry/Migration/L1Receiver.t.sol +++ b/protocol/test/foundry/Migration/L1Receiver.t.sol @@ -29,7 +29,7 @@ contract L1ReceiverFacetTest is Order, TestHelper { address RECIEVER; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); // setup basic whitelisting for testing bs.mockWhitelistToken(L2BEAN, IMockFBeanstalk.beanToBDV.selector, 10000000000, 1); @@ -138,7 +138,7 @@ contract L1ReceiverFacetTest is Order, TestHelper { vm.prank(RECIEVER); L1ReceiverFacet(BEANSTALK).issueFertilizer(owner, ids, amounts, lastBpf, proof); - assertEq(IERC1555(fertilizerAddress).balanceOf(RECIEVER, ids[0]), amounts[0]); + assertEq(IERC1555(FERTILIZER).balanceOf(RECIEVER, ids[0]), amounts[0]); // verify user cannot migrate afterwords. vm.expectRevert("L2Migration: Fertilizer have been migrated"); diff --git a/protocol/test/foundry/Migration/ReseedFunctionality.t.sol b/protocol/test/foundry/Migration/ReseedFunctionality.t.sol index 1976fa923a..7889bc5a2f 100644 --- a/protocol/test/foundry/Migration/ReseedFunctionality.t.sol +++ b/protocol/test/foundry/Migration/ReseedFunctionality.t.sol @@ -32,7 +32,6 @@ contract ReseedFunctionalityTest is TestHelper { // contracts for testing: address constant L2_BEANSTALK = address(0xD1A0060ba708BC4BCD3DA6C37EFa8deDF015FB70); - address constant FERTILIZER = address(0xFEFEFECA5375630d6950F40e564A27f6074845B5); address constant BEANSTALK_PRICE = address(0xC218F5a782b0913931DCF502FA2aA959b36Ac9E7); uint256 constant FIELD_ID = 0; diff --git a/protocol/test/foundry/Migration/ReseedMigrateL2.t.sol b/protocol/test/foundry/Migration/ReseedMigrateL2.t.sol index 705636f9af..0d47554d87 100644 --- a/protocol/test/foundry/Migration/ReseedMigrateL2.t.sol +++ b/protocol/test/foundry/Migration/ReseedMigrateL2.t.sol @@ -9,6 +9,7 @@ import {L1TokenFacet} from "contracts/beanstalk/migration/L1TokenFacet.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IDiamondCut} from "contracts/interfaces/IDiamondCut.sol"; import {IDiamondLoupe} from "contracts/interfaces/IDiamondLoupe.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; /** * @notice Tests the functionality of migration. @@ -35,7 +36,7 @@ contract reeseedMigrateL2 is TestHelper { uint256 l2ForkId; function setUp() public { - bs = IMockFBeanstalk(BEANSTALK); + bs = IMockFBeanstalk(LibConstant.BEANSTALK); // fork mainnet. mainnetForkId = vm.createFork(vm.envString("FORKING_RPC"), 20584419); @@ -43,7 +44,8 @@ contract reeseedMigrateL2 is TestHelper { l2ForkId = vm.createFork(vm.envString("ARBITRUM_FORKING_RPC"), 245539862); vm.selectFork(mainnetForkId); vm.label(address(0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f), "Arbitrum L1 Bridge"); - vm.label(BEANSTALK, "Beanstalk"); + vm.label(LibConstant.BEANSTALK, "Beanstalk"); + vm.label(BEAN, "BEAN"); // perform step 1 of the migration process. (transferring assets to the BCM). // this is done on L1. @@ -61,8 +63,8 @@ contract reeseedMigrateL2 is TestHelper { bytes4[] memory removedSelectors = generateL2MigrationSelectors(); // upgrade beanstalk with an L2 migration facet. upgradeWithNewFacets( - BEANSTALK, // upgrading beanstalk. - IMockFBeanstalk(BEANSTALK).owner(), // fetch beanstalk owner. + LibConstant.BEANSTALK, // upgrading beanstalk. + IMockFBeanstalk(LibConstant.BEANSTALK).owner(), // fetch beanstalk owner. facetNames, newFacetAddresses, facetCutActions, @@ -94,7 +96,7 @@ contract reeseedMigrateL2 is TestHelper { // verify facets have been removed. function test_facets() public view { - IDiamondLoupe.Facet[] memory facets = IDiamondLoupe(BEANSTALK).facets(); + IDiamondLoupe.Facet[] memory facets = IDiamondLoupe(LibConstant.BEANSTALK).facets(); assertEq(facets.length, 6); assertEq(facets[0].facetAddress, address(0xDFeFF7592915bea8D040499E961E332BD453C249)); // DiamondCutFacet assertEq(facets[1].facetAddress, address(0xB51D5C699B749E0382e257244610039dDB272Da0)); // DiamondLoupeFacet @@ -109,14 +111,14 @@ contract reeseedMigrateL2 is TestHelper { // active address on beanstalk. address user = address(0x1c42949f6f326Fc53E6fAaDE1A4cCB9b5b209A80); // transfer out. - address token = address(USDC); + address token = address(L1_USDC); - uint256 initialTokenBalance = IERC20(USDC).balanceOf(BEANSTALK); - uint256 initialUserBalance = IERC20(USDC).balanceOf(user); + uint256 initialTokenBalance = IERC20(L1_USDC).balanceOf(BEANSTALK); + uint256 initialUserBalance = IERC20(L1_USDC).balanceOf(user); uint256 initialInternalTokenBalance = bs.getInternalBalance(user, token); vm.prank(user); - IERC20(USDC).approve(BEANSTALK, type(uint256).max); + IERC20(L1_USDC).approve(BEANSTALK, type(uint256).max); vm.prank(user); // verify the user is able to deposit into internal balances. @@ -128,15 +130,15 @@ contract reeseedMigrateL2 is TestHelper { // initial snapshot. uint256 snapshot = vm.snapshot(); - initialUserBalance = IERC20(USDC).balanceOf(user); + initialUserBalance = IERC20(L1_USDC).balanceOf(user); initialInternalTokenBalance = bs.getInternalBalance(user, token); - initialTokenBalance = IERC20(USDC).balanceOf(BEANSTALK); + initialTokenBalance = IERC20(L1_USDC).balanceOf(BEANSTALK); vm.prank(user); // verify the user is able to transfer internal balances to other users). bs.transferToken(token, address(100010), 1e6, 1, 1); assertEq(initialUserBalance - IERC20(token).balanceOf(user), 0); assertEq(IERC20(token).balanceOf(address(100010)), 0); - assertEq(initialTokenBalance - IERC20(token).balanceOf(BEANSTALK), 0); + assertEq(initialTokenBalance - IERC20(token).balanceOf(LibConstant.BEANSTALK), 0); assertEq(initialInternalTokenBalance - bs.getInternalBalance(user, token), 1e6); assertEq(bs.getInternalBalance(address(100010), token), 1e6); @@ -204,7 +206,7 @@ contract reeseedMigrateL2 is TestHelper { function generateL2MigrationSelectors() internal view returns (bytes4[] memory facetSelectors) { // get all facets from beanstalk. facetSelectors = new bytes4[](65535); - IDiamondLoupe.Facet[] memory facets = IDiamondLoupe(BEANSTALK).facets(); + IDiamondLoupe.Facet[] memory facets = IDiamondLoupe(LibConstant.BEANSTALK).facets(); uint256 k; for (uint i; i < facets.length; i++) { IDiamondLoupe.Facet memory facet = facets[i]; diff --git a/protocol/test/foundry/Migration/ReseedState.t.sol b/protocol/test/foundry/Migration/ReseedState.t.sol index 1a21d413d8..9d769d4798 100644 --- a/protocol/test/foundry/Migration/ReseedState.t.sol +++ b/protocol/test/foundry/Migration/ReseedState.t.sol @@ -32,7 +32,6 @@ contract ReseedStateTest is TestHelper { // contracts for testing: address constant L2_BEANSTALK = address(0xD1A0060ba708BC4BCD3DA6C37EFa8deDF015FB70); - address constant FERTILIZER = address(0xFEFEFECA5375630d6950F40e564A27f6074845B5); address constant BEANSTALK_PRICE = address(0xC218F5a782b0913931DCF502FA2aA959b36Ac9E7); uint256 constant FIELD_ID = 0; diff --git a/protocol/test/foundry/convert/convert.t.sol b/protocol/test/foundry/convert/convert.t.sol index ca744ddf5e..76b0e0d621 100644 --- a/protocol/test/foundry/convert/convert.t.sol +++ b/protocol/test/foundry/convert/convert.t.sol @@ -26,9 +26,6 @@ contract ConvertTest is TestHelper { uint256 toAmount ); - // Interfaces. - MockConvertFacet convert = MockConvertFacet(BEANSTALK); - // MockTokens. MockToken weth = MockToken(WETH); @@ -39,7 +36,7 @@ contract ConvertTest is TestHelper { address well; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); well = BEAN_ETH_WELL; // init user. farmers.push(users[1]); @@ -116,7 +113,7 @@ contract ConvertTest is TestHelper { vm.expectRevert("Convert: Not enough tokens removed."); vm.prank(farmers[0]); - convert.convert(convertData, stems, amounts); + bs.convert(convertData, stems, amounts); } /** @@ -138,7 +135,7 @@ contract ConvertTest is TestHelper { amounts[0] = uint256(beanAmount); vm.expectRevert("Silo: Crate balance too low."); - convert.convert(convertData, new int96[](1), amounts); + bs.convert(convertData, new int96[](1), amounts); } //////////// BEAN -> WELL //////////// @@ -163,7 +160,7 @@ contract ConvertTest is TestHelper { vm.expectRevert("Convert: P must be >= 1."); vm.prank(farmers[0]); - convert.convert(convertData, new int96[](1), new uint256[](1)); + bs.convert(convertData, new int96[](1), new uint256[](1)); } /** @@ -199,7 +196,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], BEAN, well, expectedBeansConverted, expectedAmtOut); vm.prank(farmers[0]); - convert.convert(convertData, new int96[](1), amounts); + bs.convert(convertData, new int96[](1), amounts); assertEq(bs.getMaxAmountIn(BEAN, well), 0, "BEAN -> WELL maxAmountIn should be 0"); } @@ -231,7 +228,7 @@ contract ConvertTest is TestHelper { // vm.expectEmit(); emit Convert(farmers[0], BEAN, well, beansConverted, expectedAmtOut); vm.prank(farmers[0]); - convert.convert(convertData, new int96[](1), amounts); + bs.convert(convertData, new int96[](1), amounts); int256 newDeltaB = bs.poolCurrentDeltaB(well); @@ -270,7 +267,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], BEAN, well, beansConverted, expectedAmtOut); vm.prank(farmers[0]); - convert.convert(convertData, stems, amounts); + bs.convert(convertData, stems, amounts); // verify deltaB. assertEq( @@ -315,7 +312,7 @@ contract ConvertTest is TestHelper { vm.expectRevert("Convert: P must be < 1."); vm.prank(farmers[0]); - convert.convert(convertData, new int96[](1), new uint256[](1)); + bs.convert(convertData, new int96[](1), new uint256[](1)); } /** @@ -345,7 +342,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], well, BEAN, maxLPin, beansAddedToWell); vm.prank(farmers[0]); - convert.convert(convertData, new int96[](1), amounts); + bs.convert(convertData, new int96[](1), amounts); assertEq(bs.getMaxAmountIn(well, BEAN), 0, "WELL -> BEAN maxAmountIn should be 0"); } @@ -363,7 +360,7 @@ contract ConvertTest is TestHelper { ); vm.expectRevert("Convert: Invalid Well"); - convert.convert(convertData, new int96[](1), new uint256[](1)); + bs.convert(convertData, new int96[](1), new uint256[](1)); } /** @@ -377,7 +374,7 @@ contract ConvertTest is TestHelper { setReserves(well, bean.balanceOf(well) + deltaB, weth.balanceOf(well)); uint256 initalWellBeanBalance = bean.balanceOf(well); uint256 initalLPbalance = MockToken(well).totalSupply(); - uint256 initalBeanBalance = bean.balanceOf(BEANSTALK); + uint256 initalBeanBalance = bean.balanceOf(address(bs)); uint256 maxLpIn = bs.getMaxAmountIn(well, BEAN); lpConverted = bound(lpConverted, minLp, lpMinted / 2); @@ -403,7 +400,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], well, BEAN, lpConverted, expectedAmtOut); vm.prank(farmers[0]); - convert.convert(convertData, new int96[](1), amounts); + bs.convert(convertData, new int96[](1), amounts); // the new maximum amount out should be the difference between the deltaB and the expected amount out. assertEq( @@ -422,7 +419,7 @@ contract ConvertTest is TestHelper { "well LP balance does not equal initalLPbalance - lpConverted" ); assertEq( - bean.balanceOf(BEANSTALK), + bean.balanceOf(address(bs)), initalBeanBalance + expectedAmtOut, "bean balance does not equal initalBeanBalance + expectedAmtOut" ); @@ -439,7 +436,7 @@ contract ConvertTest is TestHelper { setReserves(well, bean.balanceOf(well) + deltaB, weth.balanceOf(well)); uint256 initalWellBeanBalance = bean.balanceOf(well); uint256 initalLPbalance = MockToken(well).totalSupply(); - uint256 initalBeanBalance = bean.balanceOf(BEANSTALK); + uint256 initalBeanBalance = bean.balanceOf(address(bs)); uint256 maxLpIn = bs.getMaxAmountIn(well, BEAN); lpConverted = bound(lpConverted, minLp, lpMinted); @@ -469,7 +466,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], well, BEAN, lpConverted, expectedAmtOut); vm.prank(farmers[0]); - convert.convert(convertData, stems, amounts); + bs.convert(convertData, stems, amounts); // the new maximum amount out should be the difference between the deltaB and the expected amount out. assertEq( @@ -488,7 +485,7 @@ contract ConvertTest is TestHelper { "well LP balance does not equal initalLPbalance - lpConverted" ); assertEq( - bean.balanceOf(BEANSTALK), + bean.balanceOf(address(bs)), initalBeanBalance + expectedAmtOut, "bean balance does not equal initalBeanBalance + expectedAmtOut" ); @@ -499,10 +496,10 @@ contract ConvertTest is TestHelper { // note: LP is minted with an price of 1000 beans. lpMinted = mintBeanLPtoUser(farmers[0], 100000e6, 1000e6); vm.startPrank(farmers[0]); - MockToken(well).approve(BEANSTALK, type(uint256).max); + MockToken(well).approve(address(bs), type(uint256).max); bs.deposit(well, lpMinted / 2, 0); - season.siloSunrise(0); + bs.siloSunrise(0); bs.deposit(well, lpMinted - (lpMinted / 2), 0); // Germinating deposits cannot convert (see {LibGerminate}). @@ -567,7 +564,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], well, well, initalAmount, initalAmount); vm.prank(farmers[0]); - (int96 toStem, , , , ) = convert.convert(convertData, stems, amounts); + (int96 toStem, , , , ) = bs.convert(convertData, stems, amounts); (uint256 updatedAmount, uint256 updatedBdv) = bs.getDeposit(farmers[0], well, toStem); // the stem of a deposit increased, because the stalkPerBdv of the deposit decreased. @@ -610,7 +607,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], well, well, initalAmount, initalAmount); vm.prank(farmers[0]); - (int96 toStem, , , , ) = convert.convert(convertData, stems, amounts); + (int96 toStem, , , , ) = bs.convert(convertData, stems, amounts); (uint256 updatedAmount, uint256 updatedBdv) = bs.getDeposit(farmers[0], well, toStem); assertEq(toStem, int96(0), "stems should be equal"); @@ -644,7 +641,7 @@ contract ConvertTest is TestHelper { vm.expectEmit(); emit Convert(farmers[0], well, well, lpCombined, lpCombined); vm.prank(farmers[0]); - convert.convert(convertData, stems, amounts); + bs.convert(convertData, stems, amounts); // verify old deposits are gone. // see `multipleWellDepositSetup` to understand the deposits. @@ -687,7 +684,7 @@ contract ConvertTest is TestHelper { // setReserves(well, bean.balanceOf(well) + deltaB, weth.balanceOf(well)); // uint256 initalWellBeanBalance = bean.balanceOf(well); // uint256 initalLPbalance = MockToken(well).totalSupply(); - // uint256 initalBeanBalance = bean.balanceOf(BEANSTALK); + // uint256 initalBeanBalance = bean.balanceOf(address(bs)); // uint256 maxLpIn = bs.getMaxAmountIn(well, BEAN); // lpConverted = bound(lpConverted, minLp, lpMinted / 2); @@ -713,7 +710,7 @@ contract ConvertTest is TestHelper { // vm.expectEmit(); // emit Convert(farmers[0], well, BEAN, lpConverted, expectedAmtOut); // vm.prank(farmers[0]); - // convert.convert( + // bs.convert( // convertData, // new int96[](1), // amounts @@ -723,6 +720,6 @@ contract ConvertTest is TestHelper { // assertEq(bs.getAmountOut(well, BEAN, bs.getMaxAmountIn(well, BEAN)), deltaB - expectedAmtOut, 'amountOut does not equal deltaB - expectedAmtOut'); // assertEq(bean.balanceOf(well), initalWellBeanBalance - expectedAmtOut, 'well bean balance does not equal initalWellBeanBalance - expectedAmtOut'); // assertEq(MockToken(well).totalSupply(), initalLPbalance - lpConverted, 'well LP balance does not equal initalLPbalance - lpConverted'); - // assertEq(bean.balanceOf(BEANSTALK), initalBeanBalance + expectedAmtOut, 'bean balance does not equal initalBeanBalance + expectedAmtOut'); + // assertEq(bean.balanceOf(address(bs)), initalBeanBalance + expectedAmtOut, 'bean balance does not equal initalBeanBalance + expectedAmtOut'); // } } diff --git a/protocol/test/foundry/farm/AdvancedFarm.t.sol b/protocol/test/foundry/farm/AdvancedFarm.t.sol index fd2a56c143..21ff1bf600 100644 --- a/protocol/test/foundry/farm/AdvancedFarm.t.sol +++ b/protocol/test/foundry/farm/AdvancedFarm.t.sol @@ -22,8 +22,8 @@ contract AdvancedFarmTest is TestHelper { address[] farmers; function setUp() public { - initializeBeanstalkTestState(true, true); - MockToken(WETH).mint(BEANSTALK, 100_000); + initializeBeanstalkTestState(true, false, true); + MockToken(WETH).mint(address(bs), 100_000); farmers = createUsers(2); mintTokensToUsers(farmers, BEAN, 100_000e6); diff --git a/protocol/test/foundry/farm/Farm.t.sol b/protocol/test/foundry/farm/Farm.t.sol index c4abecdda2..7f95c6da35 100644 --- a/protocol/test/foundry/farm/Farm.t.sol +++ b/protocol/test/foundry/farm/Farm.t.sol @@ -20,8 +20,8 @@ contract FarmTest is TestHelper { address[] farmers; function setUp() public { - initializeBeanstalkTestState(true, true); - MockToken(WETH).mint(BEANSTALK, 100_000); + initializeBeanstalkTestState(true, false, true); + MockToken(WETH).mint(address(bs), 100_000); farmers = createUsers(2); mintTokensToUsers(farmers, BEAN, 100_000e6); diff --git a/protocol/test/foundry/farm/PipelineConvert.t.sol b/protocol/test/foundry/farm/PipelineConvert.t.sol index 7b64f53e51..eb7dfbadca 100644 --- a/protocol/test/foundry/farm/PipelineConvert.t.sol +++ b/protocol/test/foundry/farm/PipelineConvert.t.sol @@ -7,12 +7,18 @@ import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; import {MockPump} from "contracts/mocks/well/MockPump.sol"; import {IWell, Call} from "contracts/interfaces/basin/IWell.sol"; import {MockToken} from "contracts/mocks/MockToken.sol"; +import {DepotFacet, AdvancedPipeCall} from "contracts/beanstalk/farm/DepotFacet.sol"; +import {PipelineConvertFacet} from "contracts/beanstalk/silo/PipelineConvertFacet.sol"; +import {AdvancedFarmCall} from "contracts/libraries/LibFarm.sol"; +import {C} from "contracts/C.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {LibConvert} from "contracts/libraries/Convert/LibConvert.sol"; import {LibRedundantMath256} from "contracts/libraries/LibRedundantMath256.sol"; import {LibDeltaB} from "contracts/libraries/Oracle/LibDeltaB.sol"; import {MockPipelineConvertFacet, AdvancedPipeCall} from "contracts/mocks/mockFacets/MockPipelineConvertFacet.sol"; import "forge-std/Test.sol"; +import {MockSiloFacet} from "contracts/mocks/mockFacets/MockSiloFacet.sol"; +import {MockPipelineConvertFacet} from "contracts/mocks/mockFacets/MockPipelineConvertFacet.sol"; contract MiscHelperContract { function returnLesser(uint256 a, uint256 b) public pure returns (uint256) { @@ -33,7 +39,10 @@ contract PipelineConvertTest is TestHelper { using LibRedundantMath256 for uint256; // Interfaces. - MockPipelineConvertFacet pipelineConvert = MockPipelineConvertFacet(BEANSTALK); + MockSiloFacet silo; + PipelineConvertFacet convert; + MockPipelineConvertFacet pipelineConvert; + DepotFacet depot; address beanEthWell = BEAN_ETH_WELL; address beanwstethWell = BEAN_WSTETH_WELL; MiscHelperContract miscHelper = new MiscHelperContract(); @@ -111,12 +120,17 @@ contract PipelineConvertTest is TestHelper { ); function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); - // initalize farmers. + // initialize farmers. farmers.push(users[1]); farmers.push(users[2]); + silo = MockSiloFacet(address(bs)); + convert = PipelineConvertFacet(address(bs)); + pipelineConvert = MockPipelineConvertFacet(address(bs)); + depot = DepotFacet(address(bs)); + // add initial liquidity to bean eth well: // prank beanstalk deployer (can be anyone) vm.prank(users[0]); @@ -215,7 +229,7 @@ contract PipelineConvertTest is TestHelper { function testBasicConvertLPToBean(uint256 amount) public { vm.pauseGasMetering(); - // well is initalized with 10000 beans. cap add liquidity + // well is initialized with 10000 beans. cap add liquidity // to reasonable amounts. amount = bound(amount, 1e6, 10000e6); @@ -246,7 +260,7 @@ contract PipelineConvertTest is TestHelper { function testConvertLPToLP(uint256 amount, uint256 inputIndex, uint256 outputIndex) public { vm.pauseGasMetering(); - // well is initalized with 10000 beans. cap add liquidity + // well is initialized with 10000 beans. cap add liquidity // to reasonable amounts. amount = bound(amount, 10e6, 5000e6); @@ -899,7 +913,7 @@ contract PipelineConvertTest is TestHelper { updateMockPumpUsingWellReserves(beanEthWell); // move foward 10 seasons so we have grown stalk - season.siloSunrise(10); + bs.siloSunrise(10); BeanToBeanTestData memory td; td.grownStalkForDeposit = bs.grownStalkForDeposit(users[1], BEAN, stem); @@ -1486,13 +1500,12 @@ contract PipelineConvertTest is TestHelper { ) public returns (int96 stem) { vm.pauseGasMetering(); // amount = bound(amount, 1e6, 5000e6); - bean.mint(user, amount); // setup array of addresses with user address[] memory users = new address[](1); users[0] = user; - (amount, stem) = setUpSiloDepositTest(amount, users); + (amount, stem) = setUpSiloDeposits(amount, users); passGermination(); } @@ -1520,7 +1533,7 @@ contract PipelineConvertTest is TestHelper { // approve spending well token to beanstalk vm.prank(users[1]); - MockToken(well).approve(BEANSTALK, type(uint256).max); + MockToken(well).approve(address(bs), type(uint256).max); vm.prank(users[1]); (, , int96 theStem) = bs.deposit(well, lpAmountOut, 0); @@ -1637,7 +1650,7 @@ contract PipelineConvertTest is TestHelper { // approve spending well token to beanstalk vm.prank(user); - MockToken(beanEthWell).approve(BEANSTALK, type(uint256).max); + MockToken(beanEthWell).approve(address(bs), type(uint256).max); } function removeEthFromWell(address user, uint256 amount) public returns (uint256 lpAmountOut) { @@ -1661,7 +1674,7 @@ contract PipelineConvertTest is TestHelper { // approve spending well token to beanstalk vm.prank(user); - MockToken(beanEthWell).approve(BEANSTALK, type(uint256).max); + MockToken(beanEthWell).approve(address(bs), type(uint256).max); } /** diff --git a/protocol/test/foundry/field/Field.t.sol b/protocol/test/foundry/field/Field.t.sol index 572421fc4d..c66016689d 100644 --- a/protocol/test/foundry/field/Field.t.sol +++ b/protocol/test/foundry/field/Field.t.sol @@ -4,7 +4,10 @@ pragma abicoder v2; import {TestHelper, LibTransfer, IMockFBeanstalk} from "test/foundry/utils/TestHelper.sol"; import {MockFieldFacet} from "contracts/mocks/mockFacets/MockFieldFacet.sol"; +import {MockSeasonFacet} from "contracts/mocks/mockFacets/MockSeasonFacet.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; import {C} from "contracts/C.sol"; +import {AppStorage, LibAppStorage} from "contracts/libraries/LibAppStorage.sol"; contract FieldTest is TestHelper { // events @@ -12,15 +15,16 @@ contract FieldTest is TestHelper { event Sow(address indexed account, uint256 fieldId, uint256 index, uint256 beans, uint256 pods); // Interfaces. - MockFieldFacet field = MockFieldFacet(BEANSTALK); + MockFieldFacet field; // test accounts address[] farmers; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); + field = MockFieldFacet(address(bs)); - // initalize farmers from farmers (farmer0 == diamond deployer) + // initialize farmers from farmers (farmer0 == diamond deployer) farmers.push(users[1]); farmers.push(users[2]); @@ -54,8 +58,8 @@ contract FieldTest is TestHelper { soil = bound(soil, 1, beans - 1); // beans less than soil. // issue `beans` to farmers - bean.mint(farmers[0], beans); - season.setSoilE(soil - 1); + IBean(BEAN).mint(farmers[0], beans); + bs.setSoilE(soil - 1); vm.prank(farmers[0]); vm.expectRevert("Field: Soil Slippage"); field.sowWithMin( @@ -253,7 +257,7 @@ contract FieldTest is TestHelper { farmer1BeansBeforeSow + farmer2BeansBeforeSow - totalAmountSown, "invalid bean supply" ); - assertEq(bean.balanceOf(BEANSTALK), 0, "beans remaining in beanstalk"); + assertEq(IBean(BEAN).balanceOf(address(bs)), 0, "beans remaining in beanstalk"); assertEq(field.totalPods(0), totalPodsIssued, "invalid total pods"); assertEq(field.totalUnharvestable(0), totalPodsIssued, "invalid unharvestable"); @@ -283,7 +287,7 @@ contract FieldTest is TestHelper { function _beforeEachSow(uint256 soilAmount, uint256 sowAmount, uint8 from) public { vm.roll(30); - season.setSoilE(soilAmount); + bs.setSoilE(soilAmount); vm.expectEmit(); emit Sow(farmers[0], 0, 0, sowAmount, (sowAmount * 101) / 100); vm.prank(farmers[0]); @@ -309,7 +313,7 @@ contract FieldTest is TestHelper { uint256 internalBalance ) public { vm.roll(30); - season.setSoilE(soilAmount); + bs.setSoilE(soilAmount); vm.expectEmit(); if (internalBalance > sowAmount) internalBalance = sowAmount; emit Sow(farmers[0], 0, 0, internalBalance, (internalBalance * 101) / 100); @@ -324,9 +328,9 @@ contract FieldTest is TestHelper { address farmer1, uint256 amount1 ) public returns (uint256, uint256, uint256, uint256) { - season.setSoilE(soil); - bean.mint(farmer0, amount0); - uint256 initalBeanBalance0 = bean.balanceOf(farmer0); + bs.setSoilE(soil); + IBean(BEAN).mint(farmer0, amount0); + uint256 initalBeanBalance0 = IBean(BEAN).balanceOf(farmer0); if (amount0 > soil) amount0 = soil; soil -= amount0; @@ -359,8 +363,8 @@ contract FieldTest is TestHelper { uint256 expectedPods ) public view { assertEq(bs.getBalance(account, BEAN), preBeanBalance - sowedAmount, "balanceOf"); - assertEq(bean.balanceOf(BEANSTALK), 0, "field balanceOf"); - assertEq(bean.totalSupply(), preTotalBalance - sowedAmount, "total supply"); + assertEq(IBean(BEAN).balanceOf(address(bs)), 0, "field balanceOf"); + assertEq(IBean(BEAN).totalSupply(), preTotalBalance - sowedAmount, "total supply"); //// FIELD STATE //// assertEq(field.plot(account, 0, 0), expectedPods, "plot"); @@ -385,7 +389,7 @@ contract FieldTest is TestHelper { uint256 pods = (sowAmount * 101) / 100; portion = bound(portion, 1, pods - 1); field.incrementTotalHarvestableE(activeField, portion); - sowAmountForFarmer(farmers[0], sowAmount); + sowForUser(farmers[0], sowAmount); plotIndexes = field.getPlotIndexesFromAccount(farmers[0], activeField); plots = field.getPlotsFromAccount(farmers[0], activeField); @@ -446,7 +450,7 @@ contract FieldTest is TestHelper { uint256 sowAmount = rand(0, 10e6); uint256 sows = rand(1, 1000); for (uint256 i; i < sows; i++) { - sowAmountForFarmer(farmers[0], sowAmount); + sowForUser(farmers[0], sowAmount); } verifyPlotIndexAndPlotLengths(farmers[0], activeField, sows); uint256 pods = (sowAmount * 101) / 100; @@ -532,7 +536,7 @@ contract FieldTest is TestHelper { field.setActiveField(j, 101); uint256 activeField = field.activeField(); for (uint256 i; i < sowsPerField; i++) { - sowAmountForFarmer(farmers[0], sowAmount); + sowForUser(farmers[0], sowAmount); } } diff --git a/protocol/test/foundry/field/Marketplace.t.sol b/protocol/test/foundry/field/Marketplace.t.sol index e03c029c27..b6f8b4b13f 100644 --- a/protocol/test/foundry/field/Marketplace.t.sol +++ b/protocol/test/foundry/field/Marketplace.t.sol @@ -10,14 +10,12 @@ contract ListingTest is TestHelper { // test accounts address[] farmers; - MockFieldFacet field = MockFieldFacet(BEANSTALK); - function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); - season.siloSunrise(0); + bs.siloSunrise(0); - // initalize farmers from farmers (farmer0 == diamond deployer) + // initialize farmers from farmers (farmer0 == diamond deployer) farmers.push(users[1]); farmers.push(users[2]); @@ -26,7 +24,7 @@ contract ListingTest is TestHelper { mintTokensToUsers(farmers, BEAN, MAX_DEPOSIT_BOUND); - field.incrementTotalSoilE(1000e18); + bs.incrementTotalSoilE(1000e18); // mine 300 blocks vm.roll(300); diff --git a/protocol/test/foundry/forkSystem/TransmitToFork.t.sol b/protocol/test/foundry/forkSystem/TransmitToFork.t.sol new file mode 100644 index 0000000000..d6ebde547e --- /dev/null +++ b/protocol/test/foundry/forkSystem/TransmitToFork.t.sol @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; +pragma abicoder v2; + +import {C} from "contracts/C.sol"; +import {LibAltC} from "test/foundry/utils/LibAltC.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; +import {TestHelper} from "test/foundry/utils/TestHelper.sol"; +import {LibTransmitOut} from "contracts/libraries/ForkSystem/LibTransmitOut.sol"; +import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol"; +import {LibBytes} from "contracts/libraries/LibBytes.sol"; +import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; +import {BeanstalkDeployer} from "test/foundry/utils/BeanstalkDeployer.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; + +/** + * @notice Tests migrations to a child fork from og Beanstalk. + */ +contract TransmitToForkTest is TestHelper { + IMockFBeanstalk newBs; + address newBsAddr; + + uint256 SRC_FIELD = 0; + uint256 DEST_FIELD = 1; + uint256 SRC_INIT_PODS = 1_000_000e6; + + uint32 constant REPLANT_SEASON = 6074; + + address ZERO_ADDR = LibConstant.ZERO_ADDRESS; + + // test accounts + address[] farmers; + address payable newBeanstalk; + + function setUp() public { + initializeBeanstalkTestState(true, false, false); + + farmers = createUsers(6); + + // max approve. + maxApproveBeanstalk(farmers); + + // Initialize well to balances. Both source and Destination share this well. + setReserves(BEAN_ETH_WELL, 1_000_000e6, 1000e18); + initializeUnripeTokens(farmers[0], 100e6, 100e18); + + setUpSiloDeposits(10_000e6, farmers); + passGermination(); + + bs.fastForward(REPLANT_SEASON); + + addFertilizerBasedOnSprouts(REPLANT_SEASON, 100e6); + + // Deploy new Beanstalk fork. + TestHelper altEcosystem = new TestHelper(); + altEcosystem.initializeBeanstalkTestState(true, true, false); + newBs = IMockFBeanstalk(altEcosystem.bs()); + newBsAddr = address(newBs); + vm.prank(deployer); + newBs.addField(); + newBs.initDestinationField(SRC_INIT_PODS); + // Set to use two fields. + altEcosystem.setRoutes_siloAndBarnAndTwoFields(); + + newBs.fastForward(100); + } + + /** + * @notice Performs a migration of all asset types from a Source to Destination fork. + */ + function test_transmitDeposits(uint256 beanDepositAmount, uint256 lpDepositAmount) public { + beanDepositAmount = bound(beanDepositAmount, 100, 100_000e6); + lpDepositAmount = bound(lpDepositAmount, 1e18, 5e18); + address user = farmers[2]; + + passGermination(); + depositForUser(user, BEAN, beanDepositAmount); + int96 beanStem = bs.stemTipForToken(BEAN); + depositForUser(user, BEAN_ETH_WELL, lpDepositAmount); + int96 lpStem = bs.stemTipForToken(BEAN_ETH_WELL); + passGermination(); + + // attempt to transfer germinating deposits + { + depositForUser(user, BEAN, beanDepositAmount); + int96 secondBeanStem = bs.stemTipForToken(BEAN); + + IMockFBeanstalk.SourceDeposit[] memory _deposits = new IMockFBeanstalk.SourceDeposit[]( + 1 + ); + _deposits[0] = IMockFBeanstalk.SourceDeposit( + BEAN, + beanDepositAmount, + secondBeanStem, + new uint256[](2), // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // populated by source + 0, // populated by source + address(0), // populated by source + 0 // populated by source + ); + + bytes[] memory _assets = new bytes[](3); + _assets[0] = abi.encode(_deposits); + _assets[1] = abi.encode(new IMockFBeanstalk.SourcePlot[](0)); + _assets[2] = abi.encode(new IMockFBeanstalk.SourceFertilizer[](0)); + + vm.expectRevert("Transmit: Cannot transmit germinating deposits"); + vm.prank(user); + bs.transmitOut(newBsAddr, _assets, abi.encode("")); + + // if one season passes, the deposit is still germinating, it should revert + + bs.siloSunrise(0); + vm.expectRevert("Transmit: Cannot transmit germinating deposits"); + vm.prank(user); + bs.transmitOut(newBsAddr, _assets, abi.encode("")); + } + + // Capture Source state. + (, uint256 beanDepositBdv) = bs.getDeposit(user, BEAN, beanStem); + assertGt(beanDepositBdv, 0, "Source Bean deposit bdv"); + uint256 lpDepositBdv; + (lpDepositAmount, lpDepositBdv) = bs.getDeposit(user, BEAN_ETH_WELL, lpStem); + assertGt(lpDepositBdv, 0, "Source LP deposit bdv"); + + uint256 migrationAmountLp = lpDepositAmount / 2; + + IMockFBeanstalk.SourceDeposit[] memory deposits = new IMockFBeanstalk.SourceDeposit[](3); + { + uint256 migrationAmount0 = beanDepositAmount / 10; + uint256 migrationAmount1 = beanDepositAmount - migrationAmount0; + + deposits[0] = IMockFBeanstalk.SourceDeposit( + BEAN, + migrationAmount0, + beanStem, + new uint256[](2), // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // populated by source + 0, // populated by source + address(0), // populated by source + 0 // populated by source + ); + deposits[1] = IMockFBeanstalk.SourceDeposit( + BEAN, + migrationAmount1, + beanStem, + new uint256[](2), // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // populated by source + 0, // populated by source + address(0), // populated by source + 0 // populated by source + ); + uint256[] memory minTokensOut = new uint256[](2); + minTokensOut[0] = 1; + minTokensOut[1] = 1; + deposits[2] = IMockFBeanstalk.SourceDeposit( + BEAN_ETH_WELL, + migrationAmountLp, + lpStem, + minTokensOut, + migrationAmountLp, // min dest lp amount, 1:1 bc shared bean & well + type(uint256).max, + 0, // populated by source + 0, // populated by source + address(0), // populated by source + 0 // populated by source + ); + + // Note that both the source and destination forks use the same Bean contract. This + // is not how the system is expected to be used, but is a limitation of constants within + // solidity, particularly in a diamond structure. Ownership limitations of Bean + // burning and minting are bypassed in MockToken, for testing purposes. + + // Check events for burning and minting of bean token. + vm.expectEmit(); + emit IERC20.Transfer(address(bs), ZERO_ADDR, migrationAmount0); + vm.expectEmit(); + emit IERC20.Transfer(address(bs), ZERO_ADDR, migrationAmount1); + vm.expectEmit(true, true, false, false); + emit IERC20.Transfer(address(bs), ZERO_ADDR, 0); // LP Burn + vm.expectEmit(true, true, false, false); + emit IERC20.Transfer(address(bs), ZERO_ADDR, 0); // Bean half burn + vm.expectEmit(); + emit IERC20.Transfer(ZERO_ADDR, newBsAddr, migrationAmount0); + vm.expectEmit(); + emit IERC20.Transfer(ZERO_ADDR, newBsAddr, migrationAmount1); + vm.expectEmit(true, true, false, false); + emit IERC20.Transfer(ZERO_ADDR, newBsAddr, 1); // Bean half mint + vm.expectEmit(true, true, false, false); + emit IERC20.Transfer(ZERO_ADDR, newBsAddr, 1); // LP mint + } + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(new IMockFBeanstalk.SourcePlot[](0)); + assets[2] = abi.encode(new IMockFBeanstalk.SourceFertilizer[](0)); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + + // Verify Source deposits. + { + (uint256 sourceDepositAmount, uint256 sourceDepositBdv) = bs.getDeposit( + user, + BEAN, + beanStem + ); + assertEq(sourceDepositAmount, 0, "Source bean deposit amt"); + assertEq(sourceDepositBdv, 0, "Source bean deposit bdv"); + (sourceDepositAmount, sourceDepositBdv) = bs.getDeposit(user, BEAN_ETH_WELL, lpStem); + assertEq( + sourceDepositAmount, + lpDepositAmount - migrationAmountLp, + "Source lp deposit amt" + ); + assertLt(sourceDepositBdv, lpDepositBdv, "Source lp deposit bdv"); + } + // Verify Destination deposits. + { + uint256[] memory destDepositIds = newBs + .getTokenDepositsForAccount(user, BEAN) + .depositIds; + assertEq(destDepositIds.length, 1, "Dest deposit count"); + (address token, int96 stem) = LibBytes.unpackAddressAndStem(destDepositIds[0]); + (uint256 destinationDepositAmount, uint256 destinationDepositBdv) = newBs.getDeposit( + user, + BEAN, + stem + ); + assertLt(stem, newBs.stemTipForToken(token), "Dest Bean deposit stem too high"); + assertEq(beanDepositAmount, destinationDepositAmount, "Dest Bean deposit amount"); + assertEq(beanDepositBdv, destinationDepositBdv, "Dest Bean deposit bdv"); + + destDepositIds = newBs.getTokenDepositsForAccount(user, BEAN_ETH_WELL).depositIds; + assertEq(destDepositIds.length, 1, "Dest deposit count"); + (token, stem) = LibBytes.unpackAddressAndStem(destDepositIds[0]); + (destinationDepositAmount, destinationDepositBdv) = newBs.getDeposit( + user, + BEAN_ETH_WELL, + stem + ); + assertLt(stem, newBs.stemTipForToken(BEAN_ETH_WELL), "Dest lp dep stem too high"); + assertEq(migrationAmountLp, destinationDepositAmount, "Dest lp deposit amount"); + assertLt(0, destinationDepositBdv, "Dest lp deposit bdv"); + } + } + + /** + * @notice Performs a migration of all asset types from a Source to Destination Beanstalk. + */ + function test_transmitPlots(uint256 sowAmount) public { + sowAmount = bound(sowAmount, 100, 1000e6); + address user = farmers[2]; + + uint256 podsPerSow = sowForUser(user, sowAmount); + uint256 partialAmt = podsPerSow / 3; + uint256 remainingAmt = podsPerSow - partialAmt; + sowForUser(user, sowAmount); + + IMockFBeanstalk.SourcePlot[] memory plots = new IMockFBeanstalk.SourcePlot[](2); + { + // Initial Migration of a Plot. With index != 0. + plots[0] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + podsPerSow, // plotId + podsPerSow, // amount + 0 // prevDestIndex + ); + // Migration of a plot prior to latest transmitted plot. + plots[1] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + 0, // plotId + partialAmt, // amount + 0 // prevDestIndex + ); + } + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(new IMockFBeanstalk.SourceDeposit[](0)); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(new IMockFBeanstalk.SourceFertilizer[](0)); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + + // Verify Source plots. + { + assertEq(bs.plot(user, SRC_FIELD, 0), 0, "1st src amt"); + assertEq(bs.plot(user, SRC_FIELD, partialAmt), remainingAmt, "1.5 src amt"); + assertEq(bs.plot(user, SRC_FIELD, podsPerSow), 0, "2nd src amt"); + assertEq(bs.plot(ZERO_ADDR, SRC_FIELD, 0), partialAmt, "1st src null amt"); + assertEq(bs.plot(ZERO_ADDR, SRC_FIELD, partialAmt), 0, "1.5 src null amt"); + assertEq(bs.plot(ZERO_ADDR, SRC_FIELD, podsPerSow), podsPerSow, "2nd src null amt"); + } + // Verify Destination plots. + { + assertEq(newBs.plot(user, DEST_FIELD, 0), partialAmt, "1st dest amt"); + assertEq(newBs.plot(ZERO_ADDR, DEST_FIELD, partialAmt), remainingAmt, "1.5 dest amt"); + assertEq(newBs.plot(user, DEST_FIELD, podsPerSow), podsPerSow, "2nd dest amt"); + } + + // Migrate a plot that is already harvestable and a plot that is beyond the source init length. + uint256 pods; + user = farmers[4]; + { + uint256 alreadyHarvestableIndex = bs.podIndex(SRC_FIELD); + uint256 pods0 = sowForUser(user, sowAmount); + newBs.sunSunrise(500_000e6, 0); + + sowForUser(farmers[3], 2_000_000e6); + uint256 beyondEndOfLineIndex = bs.podIndex(SRC_FIELD); + uint256 pods1 = sowForUser(user, sowAmount) / 2; // partial + + // Initial Migration of a Plot. With index != 0. + plots[0] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + alreadyHarvestableIndex, // plotId + pods0, // amount + 0 // prevDestIndex, not used bc already harvestable + ); + // Migration of a plot prior to latest transmitted plot. + plots[1] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + beyondEndOfLineIndex, // plotId + pods1, // amount + 0 // prevDestIndex, not used bc beyond end of line + ); + pods = pods0 + pods1; + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(new IMockFBeanstalk.SourceDeposit[](0)); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(new IMockFBeanstalk.SourceFertilizer[](0)); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + } + // Verify source plots slashing. + { + bs.incrementTotalHarvestableE(0, podsPerSow * 2); + uint256[] memory plotIndices = new uint256[](2); + plotIndices[0] = 0; + plotIndices[1] = podsPerSow; + uint256 initBeanBalance = bean.balanceOf(farmers[5]); + vm.expectEmit(); + emit IMockFBeanstalk.Harvest(farmers[5], SRC_FIELD, plotIndices, 0); + vm.prank(farmers[5]); + bs.harvest(0, plotIndices, 0); + assertEq(bean.balanceOf(farmers[5]), initBeanBalance, "Beans received from slashing"); + // at this point, only slashed plots have been processed, no beans harvested + assertEq(bs.totalHarvested(SRC_FIELD), 0, "total harvested"); + assertEq(bs.totalSlashed(SRC_FIELD), bs.totalProcessed(SRC_FIELD), "total slashed"); + } + // Verify Destination plots by harvesting. + { + newBs.sunSunrise(10_000_000e6, 0); + uint256[] memory plotIds = newBs.getPlotIndexesFromAccount(user, DEST_FIELD); + for (uint256 i; i < plotIds.length; i++) { + assertGe(plotIds[i], SRC_INIT_PODS, "plot not pushed"); + } + assertEq(plotIds.length, 2, "dest plot count"); + vm.expectEmit(); + emit IMockFBeanstalk.Harvest(user, DEST_FIELD, plotIds, pods); + vm.prank(user); + newBs.harvest(DEST_FIELD, plotIds, 0); + } + } + + /** + * @notice Performs a migration of all asset types from a Source to Destination fork. + */ + function test_transmitFertilizer(uint256 ethAmount) public { + address user = farmers[2]; + + uint128 id0 = bs.getEndBpf(); + uint256 fertAmount0 = buyFertForUser(user, ethAmount); + uint256 transAmount0 = fertAmount0; + passGermination(); // change the fert id + uint128 id1 = bs.getEndBpf(); + uint256 fertAmount1 = buyFertForUser(user, ethAmount); + uint256 transAmount1 = fertAmount1; + uint256 totalTrans = transAmount0 + transAmount1; + + // Rinsable sprouts at source. Very small to avoid finishing any fert. + bs.sunSunrise(100, 0); + + uint256 srcInitUnfert = bs.totalUnfertilizedBeans(); + uint256 srcInitActiveFert = bs.getActiveFertilizer(); + + IMockFBeanstalk.SourceFertilizer[] memory ferts = new IMockFBeanstalk.SourceFertilizer[](2); + { + ferts[0] = IMockFBeanstalk.SourceFertilizer( + id0, // id + transAmount0, // amount + 0 // _remainingBpf + ); + ferts[1] = IMockFBeanstalk.SourceFertilizer( + id1, // id + transAmount1, // amount + 0 // _remainingBpf + ); + + // Note that both the source and destination forks use the same Fertilizer contract. This + // is not how the system is expected to be used, but is a limitation of constants within + // solidity, particularly in a diamond structure. Ownership limitations of Fertilizer + // burning and minting are bypassed in MockFertilizer, for testing purposes. + + // Check events for expected burning and minting of Fertilizer. + vm.expectEmit(); + emit IERC1155.TransferSingle(address(bs), user, ZERO_ADDR, id0, transAmount0); + vm.expectEmit(); + emit IERC1155.TransferSingle(address(bs), user, ZERO_ADDR, id1, transAmount1); + vm.expectEmit(); + emit IERC1155.TransferSingle(newBsAddr, ZERO_ADDR, user, id0, transAmount0); + vm.expectEmit(); + emit IERC1155.TransferSingle(newBsAddr, ZERO_ADDR, user, id1, transAmount1); + } + + vm.prank(user); + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(new IMockFBeanstalk.SourceDeposit[](0)); + assets[1] = abi.encode(new IMockFBeanstalk.SourcePlot[](0)); + assets[2] = abi.encode(ferts); + + bs.transmitOut( + newBsAddr, // Transmit into self + assets, + abi.encode("") + ); + + // Verify Source fertilizer state. + { + assertLt(bs.totalUnfertilizedBeans(), srcInitUnfert, "Src unfert amt"); + assertEq(bs.getActiveFertilizer(), srcInitActiveFert - totalTrans, "Src afert amt"); + } + // Verify Destination fertilizer. + { + assertEq(newBs.getFertilizer(id0), transAmount0, "Dest fert0 amt"); + assertEq(newBs.getFertilizer(id1), transAmount1, "Dest fert1 amt"); + assertGt(newBs.totalUnfertilizedBeans(), totalTrans, "Dest unfert"); + assertEq(newBs.getActiveFertilizer(), totalTrans, "Dest fert1 amt"); + } + // Balance of Fertilizer is unchanged, since both src and dest use the same Fert contract. + { + assertEq(fertilizer.balanceOf(user, id0), fertAmount0, "fert0 amt"); + assertEq(fertilizer.balanceOf(user, id1), fertAmount1, "fert1 amt"); + } + // Fert is rinsible at destination. + { + uint256[] memory ids = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + + // Empty rinse. + vm.expectEmit(); + emit IMockFBeanstalk.ClaimFertilizer(ids, 0); + vm.prank(user); + newBs.claimFertilized(ids, 0); + + // Non empty rinse. + // This check will fail if a partial fert is transmitted, due to shared Fert contract. + uint256 mintedBeans = 300e6; + newBs.sunSunrise(int256(mintedBeans), 0); + vm.expectEmit(); + emit IERC20.Transfer(address(newBs), user, mintedBeans / 3 - newBs.leftoverBeans()); + vm.prank(user); + newBs.claimFertilized(ids, 0); + } + } + + /** + * @notice Performs migrations that should revert. + * @dev Can revert at source if assets do not exist or destination if assets not compatible. + */ + function test_transmitRevert() public { + address user = farmers[2]; + + // Deposit reverts. + { + IMockFBeanstalk.SourceDeposit[] memory deposits = new IMockFBeanstalk.SourceDeposit[]( + 1 + ); + IMockFBeanstalk.SourcePlot[] memory plots = new IMockFBeanstalk.SourcePlot[](0); + IMockFBeanstalk.SourceFertilizer[] + memory fertilizer = new IMockFBeanstalk.SourceFertilizer[](1); + + // Revert Deposit does not exist. + deposits[0] = IMockFBeanstalk.SourceDeposit( + BEAN, + 100e6, + 1, + new uint256[](2), + 0, + 0, + 0, + 0, + address(0), + 0 + ); + vm.expectRevert(); + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(fertilizer); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + + // Revert LP amount out too high. + uint256 lpDepositAmount = 100e18; + depositForUser(user, BEAN_ETH_WELL, lpDepositAmount); + int96 lpStem = bs.stemTipForToken(BEAN_ETH_WELL); + passGermination(); + uint256[] memory minTokensOut = new uint256[](2); + minTokensOut[0] = 1; + minTokensOut[1] = 1; + uint256 lpAmountOut = 1_000_000e18; + deposits[0] = IMockFBeanstalk.SourceDeposit( + BEAN_ETH_WELL, + lpDepositAmount, + lpStem, + minTokensOut, + lpAmountOut, + type(uint256).max, + 0, // populated by source + 0, // populated by source + address(0), // populated by source + 0 // populated by source + ); + vm.expectRevert(); // 0xd58ad03f // error SlippageOut + + assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(fertilizer); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + } + + // Plot reverts. + { + IMockFBeanstalk.SourceDeposit[] memory deposits = new IMockFBeanstalk.SourceDeposit[]( + 0 + ); + IMockFBeanstalk.SourcePlot[] memory plots = new IMockFBeanstalk.SourcePlot[](1); + IMockFBeanstalk.SourceFertilizer[] + memory fertilizer = new IMockFBeanstalk.SourceFertilizer[](1); + + // Revert Plot does not exist. + plots[0] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + 420, // plotId + 69, // amount + 0 // existingIndex + ); + vm.expectRevert(); + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(fertilizer); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + + // Revert bad existing index. + uint256 podsPerSow = sowForUser(user, 1000e6); + plots[0] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + 0, // plotId + podsPerSow, // amount + 1 // existingIndex + ); + vm.expectRevert("existingIndex non null"); + + assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(fertilizer); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + } + + // Fertilizer Reverts. + // Plot reverts. + { + IMockFBeanstalk.SourceDeposit[] memory deposits = new IMockFBeanstalk.SourceDeposit[]( + 0 + ); + IMockFBeanstalk.SourcePlot[] memory plots = new IMockFBeanstalk.SourcePlot[](0); + IMockFBeanstalk.SourceFertilizer[] + memory fertilizer = new IMockFBeanstalk.SourceFertilizer[](1); + + // Revert Fert does not exist. + fertilizer[0] = IMockFBeanstalk.SourceFertilizer( + 60, // id + 100, // amount + 0 // _remainingBpf + ); + vm.expectRevert(); + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(fertilizer); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + } + } + + /** + * @notice Performs a migration of all asset types from a Source to Destination Beanstalk. + */ + function test_transmitAll() public { + address user = farmers[2]; + IMockFBeanstalk.SourceDeposit[] memory deposits = new IMockFBeanstalk.SourceDeposit[](1); + IMockFBeanstalk.SourcePlot[] memory plots = new IMockFBeanstalk.SourcePlot[](1); + IMockFBeanstalk.SourceFertilizer[] + memory fertilizer = new IMockFBeanstalk.SourceFertilizer[](1); + + uint256 beanDepositAmount = 10_000e6; + depositForUser(user, BEAN, beanDepositAmount); + int96 beanStem = bs.stemTipForToken(BEAN); + passGermination(); + deposits[0] = IMockFBeanstalk.SourceDeposit( + BEAN, + beanDepositAmount, + beanStem, + new uint256[](2), // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // Not used for Bean deposits. + 0, // populated by source + 0, // populated by source + address(0), // populated by source + 0 // populated by source + ); + + uint256 sowAmount = 1000e6; + uint256 plotIndex = bs.podIndex(SRC_FIELD); + uint256 podsAmount = sowForUser(user, sowAmount); + // Initial Migration of a Plot. With index != 0. + plots[0] = IMockFBeanstalk.SourcePlot( + SRC_FIELD, // fieldId + plotIndex, // plotId + podsAmount, // amount + 0 // prevDestIndex + ); + + uint256 ethAmountFert = 1.5e18; + uint128 fertId = bs.getEndBpf(); + uint256 fertAmount0 = buyFertForUser(user, ethAmountFert); + fertilizer[0] = IMockFBeanstalk.SourceFertilizer( + fertId, // id + fertAmount0, // amount + 0 // _remainingBpf + ); + + bytes[] memory assets = new bytes[](3); + assets[0] = abi.encode(deposits); + assets[1] = abi.encode(plots); + assets[2] = abi.encode(fertilizer); + + vm.prank(user); + bs.transmitOut(newBsAddr, assets, abi.encode("")); + } +} diff --git a/protocol/test/foundry/invariantTesting/BeanstalkInvariants.t.sol b/protocol/test/foundry/invariantTesting/BeanstalkInvariants.t.sol index 1897f71e85..2e4e38ab21 100644 --- a/protocol/test/foundry/invariantTesting/BeanstalkInvariants.t.sol +++ b/protocol/test/foundry/invariantTesting/BeanstalkInvariants.t.sol @@ -24,7 +24,7 @@ contract BeanstalkInvariants is TestHelper { function setUp() public { // vm.createSelectFork(vm.envString("FORKING_RPC"), 19976370); - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); // Initialize well to balances. (1000 BEAN/ETH) addLiquidityToWell( diff --git a/protocol/test/foundry/invariantTesting/README.md b/protocol/test/foundry/invariantTesting/README.md deleted file mode 100644 index 53aa6137f5..0000000000 --- a/protocol/test/foundry/invariantTesting/README.md +++ /dev/null @@ -1,55 +0,0 @@ -## DEPRECATED -Beanstalk is too complex of a system to meaningfully test with Invariant testing. There is not -a finite set of actions that a user could take, due to Pipeline and convert generalization. Convert -also adds intractable complexity, because it can have an arbitrary effect on user positions. Further, -the complexity and risk of beanstalk is primarily in specifically crafted attacks, rather than -internal accounting failures, due to the upgradeable nature of the contracts. Due to these -factors it was decided to discontinue system-wide invariant testing. The infrastructure here -is kept for reference. - -# Invariant Testing -These tests implement invariant based testing of the entire Beanstalk system. - -Note that these are *not* necessarily tests designed for the invariants seen in -Invariable.sol (although they are also exercised). Instead this is invariant -testing in its more generalized definition. A semi-random set of interactions -with Beanstalk with test-only invariants that verify if the state is healthy. - -### Invariant Ideas - -- All plots are harvestable/transferable -- All deposits can be accessed -- All Fert can be transferred -- Sunrise can always be called after 1 hour -- Soil <= tw deltaB -... - - -### Actions - -Silo -- Deposit -- Withdraw -- Convert -- Farm / Mow -- Deposit transfer - -Field -- Sow + Harvest -- Pod transfer + market buy/sell - -Barn -- Buy Fert + Rinse -- Fert transfer - -Sun -- Sunrise / gm (+ SoP) - -Not Applicable -- Pipeline/Depot -- Advanced Farm -- Tractor -- Diamond functions - -### References -https://medium.com/cyfrin/invariant-testing-enter-the-matrix-c71363dea37e diff --git a/protocol/test/foundry/silo/Oracle.t.sol b/protocol/test/foundry/silo/Oracle.t.sol index e16beca90d..9129477737 100644 --- a/protocol/test/foundry/silo/Oracle.t.sol +++ b/protocol/test/foundry/silo/Oracle.t.sol @@ -12,18 +12,19 @@ import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IWell} from "contracts/interfaces/basin/IWell.sol"; import "forge-std/console.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; /** * @notice Tests the functionality of the Oracles. */ contract OracleTest is TestHelper { function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); } function testUniswapOracleImplementation() public { // encode type 0x01 - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken( WBTC, IMockFBeanstalk.Implementation( @@ -33,12 +34,12 @@ contract OracleTest is TestHelper { abi.encode(LibChainlinkOracle.FOUR_HOUR_TIMEOUT) ) ); - uint256 price = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); + uint256 price = OracleFacet(address(bs)).getUsdTokenPrice(WBTC); assertEq(price, 0.00002e8, "price using encode type 0x01"); setupUniswapWBTCOracleImplementation(); - price = OracleFacet(BEANSTALK).getTokenUsdPrice(WBTC); + price = OracleFacet(address(bs)).getTokenUsdPrice(WBTC); // 1 USDC will get ~500 satoshis of BTC at $50k // 1 USDC = 1e6 // 1 wBTC = 1e8 @@ -48,28 +49,6 @@ contract OracleTest is TestHelper { assertApproxEqRel(price, 50000e6, 0.001e18, "price using encode type 0x02"); } - function test_uniswap_external() public { - setupUniswapWBTCOracleImplementation(); - - // exercise TokenUsd price and UsdToken price - uint256 tokenUsdPriceFromExternal = OracleFacet(BEANSTALK).getTokenUsdPriceFromExternal( - WBTC, - 0 - ); - assertApproxEqRel( - tokenUsdPriceFromExternal, - 50000e6, - 0.001e18, - "tokenUsdPriceFromExternal" - ); - - uint256 usdTokenPriceFromExternal = OracleFacet(BEANSTALK).getUsdTokenPriceFromExternal( - WBTC, - 0 - ); - assertEq(usdTokenPriceFromExternal, 0.00002e8, "usdTokenPriceFromExternal"); // e8 because wbtc has 8 decimals - } - /** * @notice verifies functionality with LSDChainlinkOracle.sol. */ @@ -105,8 +84,8 @@ contract OracleTest is TestHelper { mockAddRound(ETH_USD_CHAINLINK_PRICE_AGGREGATOR, 1200e6, 600); // query price - uint256 usdTokenPrice = OracleFacet(BEANSTALK).getUsdTokenTwap(WETH, 3600); - uint256 tokenUsdPrice = OracleFacet(BEANSTALK).getTokenUsdTwap(WETH, 3600); + uint256 usdTokenPrice = OracleFacet(address(bs)).getUsdTokenTwap(WETH, 3600); + uint256 tokenUsdPrice = OracleFacet(address(bs)).getTokenUsdTwap(WETH, 3600); // verify math: // 1200 + 1000 + 1100 + 1000 + 900 + 1200 = 6400 / 6 = 1066.666666e6 @@ -125,8 +104,8 @@ contract OracleTest is TestHelper { mockAddRound(ETH_USD_CHAINLINK_PRICE_AGGREGATOR, 1200e6, 50); // query price - uint256 usdTokenPrice = OracleFacet(BEANSTALK).getUsdTokenTwap(WETH, 3600); - uint256 tokenUsdPrice = OracleFacet(BEANSTALK).getTokenUsdTwap(WETH, 3600); + uint256 usdTokenPrice = OracleFacet(address(bs)).getUsdTokenTwap(WETH, 3600); + uint256 tokenUsdPrice = OracleFacet(address(bs)).getTokenUsdTwap(WETH, 3600); // verify math: // (1200 * 600) + (1000 * 1000) + (1100 * 500) + (1000 * 300) + (900 * 1150) + (1200 * 50) / 3600 = 1018.055555 * 1e6 @@ -218,7 +197,7 @@ contract OracleTest is TestHelper { } function testGetOracleImplementationForToken() public { - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken( WBTC, IMockFBeanstalk.Implementation( @@ -251,7 +230,7 @@ contract OracleTest is TestHelper { function testGetTokenPrice() public { // change encode type to 0x02 for wbtc: - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken( WBTC, IMockFBeanstalk.Implementation( @@ -263,25 +242,25 @@ contract OracleTest is TestHelper { ); // token price is number of dollars per token, i.e. 50000 USD for 1 WBTC - uint256 tokenPriceEth = OracleFacet(BEANSTALK).getTokenUsdPrice(WETH); // 1000e6 + uint256 tokenPriceEth = OracleFacet(address(bs)).getTokenUsdPrice(LibConstant.WETH); // 1000e6 assertEq(tokenPriceEth, 1000e6, "getTokenUsdPrice eth"); // number of tokens received per dollar - uint256 usdPriceEth = OracleFacet(BEANSTALK).getUsdTokenPrice(WETH); // 1e15 which is 1e18 (1 eth in wei) / 1000 (weth price 1000), you get 1/1000th of 1 eth for $1 + uint256 usdPriceEth = OracleFacet(address(bs)).getUsdTokenPrice(LibConstant.WETH); // 1e15 which is 1e18 (1 eth in wei) / 1000 (weth price 1000), you get 1/1000th of 1 eth for $1 assertEq(usdPriceEth, 1e18 / 1000, "getUsdTokenPrice eth"); - uint256 tokenPriceWBTC = OracleFacet(BEANSTALK).getTokenUsdPrice(WBTC); // should be 50000e6 + uint256 tokenPriceWBTC = OracleFacet(address(bs)).getTokenUsdPrice(WBTC); // should be 50000e6 assertEq(tokenPriceWBTC, 50000e6, "getTokenUsdPrice wbtc"); // number of tokens received per dollar - uint256 usdPriceWBTC = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); // $1 = 0.00002 wbtc, wbtc is 8 decimals, + uint256 usdPriceWBTC = OracleFacet(address(bs)).getUsdTokenPrice(WBTC); // $1 = 0.00002 wbtc, wbtc is 8 decimals, assertEq(usdPriceWBTC, 0.00002e8, "getUsdTokenPrice wbtc"); } // test provided by T1MOH function test_getUsdTokenPrice_whenExternalToken_priceIsInvalid() public { // pre condition: encode type 0x01 - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken( WBTC, IMockFBeanstalk.Implementation( @@ -293,12 +272,12 @@ contract OracleTest is TestHelper { ); // WETH price is 1000 - uint256 priceWETH = OracleFacet(BEANSTALK).getUsdTokenPrice(WETH); - assertEq(priceWETH, 1e15); // 1e18/1e3 = 1e15 + uint256 priceWETH = OracleFacet(address(bs)).getUsdTokenPrice(WETH); + assertEq(priceWETH, 1e15, "weth price invalid"); // 1e18/1e3 = 1e15 // WBTC price is 50000 - uint256 priceWBTC = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); - assertEq(priceWBTC, 0.00002e8); // adjusted to 8 decimals + uint256 priceWBTC = OracleFacet(address(bs)).getUsdTokenPrice(WBTC); + assertEq(priceWBTC, 0.00002e8, "wbtc price invalid"); // adjusted to 8 decimals } function testForkMainnetWBTCOracle() public { @@ -306,12 +285,12 @@ contract OracleTest is TestHelper { setupUniswapWBTCOracleImplementation(); - uint256 priceWBTCmillion = OracleFacet(BEANSTALK).getMillionUsdPrice(WBTC, 0); + uint256 priceWBTCmillion = OracleFacet(BEANSTALK).getMillionUsdPrice(L1_WBTC, 0); // 1e(8+6)/1684341342 = 59370.3885943091 assertEq(priceWBTCmillion, 1684454192); // $1,000,000 buys 1684341342 at BTC price of 6148186669379 per USDC and USDC 99993272. // 1e8/1684 = 59382.4228028504 - uint256 priceWBTC = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); + uint256 priceWBTC = OracleFacet(BEANSTALK).getUsdTokenPrice(L1_WBTC); assertEq(priceWBTC, 1684); // $1 buys 1683 satoshi at BTC price of 6148186669379 per USDC and USDC 99993272. } @@ -334,7 +313,7 @@ contract OracleTest is TestHelper { setupUniswapWstethOracleImplementation(); - uint256 priceWSTETH = OracleFacet(BEANSTALK).getUsdTokenPrice(WSTETH); + uint256 priceWSTETH = OracleFacet(BEANSTALK).getUsdTokenPrice(LibConstant.L1_WSTETH); assertEq(priceWSTETH, 334243752683826); } @@ -353,7 +332,7 @@ contract OracleTest is TestHelper { // deal didn't seem to work with wbtc, so instead, transfer from a wbtc whale vm.prank(WBTC_WHALE); - IERC20(WBTC).transfer(BEAN_WBTC_WELL, 2e8); // 2 wbtc + IERC20(L1_WBTC).transfer(BEAN_WBTC_WELL, 2e8); // 2 wbtc deal(address(BEAN), BEAN_WBTC_WELL, 117989199462, true); // approx 2 btc worth of beans IWell(BEAN_WBTC_WELL).sync(users[0], 0); @@ -367,9 +346,9 @@ contract OracleTest is TestHelper { //////////// Helper Functions //////////// function setupUniswapWBTCOracleImplementation() public { - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken( - WBTC, + L1_WBTC, IMockFBeanstalk.Implementation( WBTC_USDC_03_POOL, bytes4(0), @@ -379,9 +358,9 @@ contract OracleTest is TestHelper { ); // also uniswap relies on having a chainlink oracle for the dollar-denominated token, in this case USDC - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken( - USDC, + L1_USDC, IMockFBeanstalk.Implementation( USDC_USD_CHAINLINK_PRICE_AGGREGATOR, bytes4(0), @@ -421,7 +400,7 @@ contract OracleTest is TestHelper { function setupUniswapWstethOracleImplementation() internal { vm.prank(BEANSTALK); bs.updateOracleImplementationForToken( - WSTETH, + L1_WSTETH, IMockFBeanstalk.Implementation( WSTETH_ETH_001_POOL, bytes4(0), diff --git a/protocol/test/foundry/silo/OracleTimeout.t.sol b/protocol/test/foundry/silo/OracleTimeout.t.sol index 55c738214f..ff4f4c67c7 100644 --- a/protocol/test/foundry/silo/OracleTimeout.t.sol +++ b/protocol/test/foundry/silo/OracleTimeout.t.sol @@ -29,13 +29,13 @@ contract PriceTesterWstethETH is Diamond, Utils { data: abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) // reviewer: use encode or encodePacked? }); - s.sys.oracleImplementation[USDC] = impl; - uint price = LibUsdOracle.getUsdPrice(USDC); + s.sys.oracleImplementation[L1_USDC] = impl; + uint price = LibUsdOracle.getUsdPrice(L1_USDC); assertGt(price, 0, "price should be greater than 0 on block 20008200"); vm.createSelectFork(vm.envString("FORKING_RPC"), 20253304 + 5); - price = LibUsdOracle.getUsdPrice(USDC); + price = LibUsdOracle.getUsdPrice(L1_USDC); assertGt(price, 0, "price should be greater than 0 on block 20253304 + 5"); // revert back to this block, use 4 hour timeout, should return price of 0 @@ -47,9 +47,9 @@ contract PriceTesterWstethETH is Diamond, Utils { encodeType: bytes1(0x01), data: abi.encode(LibChainlinkOracle.FOUR_HOUR_TIMEOUT) }); - s.sys.oracleImplementation[USDC] = impl; + s.sys.oracleImplementation[L1_USDC] = impl; - price = LibUsdOracle.getUsdPrice(USDC); + price = LibUsdOracle.getUsdPrice(L1_USDC); assertEq(price, 0, "price should be 0 on block 20008200"); } } diff --git a/protocol/test/foundry/silo/Silo.t.sol b/protocol/test/foundry/silo/Silo.t.sol index cc66dda083..18632814e4 100644 --- a/protocol/test/foundry/silo/Silo.t.sol +++ b/protocol/test/foundry/silo/Silo.t.sol @@ -11,16 +11,13 @@ import {MockToken} from "contracts/mocks/MockToken.sol"; * @notice Tests the functionality of the Silo. */ contract SiloTest is TestHelper { - // Interfaces. - MockSiloFacet silo = MockSiloFacet(BEANSTALK); - // test accounts address[] farmers; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); - // initalize farmers from farmers (farmer0 == diamond deployer) + // initialize farmers from farmers (farmer0 == diamond deployer) farmers.push(users[1]); farmers.push(users[2]); diff --git a/protocol/test/foundry/silo/Whitelist.t.sol b/protocol/test/foundry/silo/Whitelist.t.sol index b2d9f1b248..5372153ef3 100644 --- a/protocol/test/foundry/silo/Whitelist.t.sol +++ b/protocol/test/foundry/silo/Whitelist.t.sol @@ -14,6 +14,8 @@ import {Implementation} from "contracts/beanstalk/storage/System.sol"; import {MockOracle} from "contracts/mocks/MockOracle.sol"; import {GaugePointFacet} from "contracts/beanstalk/sun/GaugePoints/GaugePointFacet.sol"; import {LiquidityWeightFacet} from "contracts/beanstalk/sun/LiquidityWeightFacet.sol"; +import {MockToken} from "contracts/mocks/MockToken.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; contract MockWellToken { address constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; @@ -85,7 +87,7 @@ contract WhitelistTest is TestHelper { event DewhitelistToken(address indexed token); function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); } // reverts if not owner. @@ -106,43 +108,6 @@ contract WhitelistTest is TestHelper { ); } - function test_whitelistRevertInvalidgpImplementation(uint i) public prank(BEANSTALK) { - bytes4 gpSelector = bytes4(keccak256(abi.encode(i))); - - vm.expectRevert("Whitelist: Invalid GaugePoint Implementation"); - bs.whitelistToken( - address(0), - IMockFBeanstalk.beanToBDV.selector, - 0, - 0, - bytes1(0), - 0, - 0, - IMockFBeanstalk.Implementation(address(0), bytes4(0), bytes1(0), new bytes(0)), - IMockFBeanstalk.Implementation(address(0), gpSelector, bytes1(0), new bytes(0)), - IMockFBeanstalk.Implementation(address(0), bytes4(0), bytes1(0), new bytes(0)) - ); - } - - function test_whitelistRevertInvalidlwImplementation(uint i) public prank(BEANSTALK) { - bytes4 gpSelector = IMockFBeanstalk.defaultGaugePointFunction.selector; - bytes4 lwSelector = bytes4(keccak256(abi.encode(i))); - - vm.expectRevert("Whitelist: Invalid LiquidityWeight Implementation"); - bs.whitelistToken( - address(0), - IMockFBeanstalk.beanToBDV.selector, - 0, - 0, - bytes1(0), - 0, - 0, - IMockFBeanstalk.Implementation(address(0), bytes4(0), bytes1(0), new bytes(0)), - IMockFBeanstalk.Implementation(address(0), gpSelector, bytes1(0), new bytes(0)), - IMockFBeanstalk.Implementation(address(0), lwSelector, bytes1(0), new bytes(0)) - ); - } - function test_whitelistRevertInvalidOracleImplementation(uint i) public prank(BEANSTALK) { address token = address(new MockWellToken()); bytes4 gpSelector = IMockFBeanstalk.defaultGaugePointFunction.selector; @@ -217,7 +182,7 @@ contract WhitelistTest is TestHelper { uint48 stalkIssuedPerBdv, uint128 gaugePoints, uint64 optimalPercentDepositedBdv - ) public prank(BEANSTALK) { + ) public prank(LibConstant.BEANSTALK) { address token = address(new MockWellToken()); bytes4 bdvSelector = IMockFBeanstalk.wellBdv.selector; IMockFBeanstalk.Implementation memory oracleImplementation = IMockFBeanstalk.Implementation( @@ -340,7 +305,7 @@ contract WhitelistTest is TestHelper { /** * @notice validates general dewhitelist functionality. */ - function test_dewhitelist(uint256 i, uint256 season) public prank(BEANSTALK) { + function test_dewhitelist(uint256 i, uint256 season) public prank(address(bs)) { season = bound(season, 1, type(uint32).max); bs.teleportSunrise(uint32(season)); address[] memory tokens = bs.getWhitelistedTokens(); diff --git a/protocol/test/foundry/sun/Cases.t.sol b/protocol/test/foundry/sun/Cases.t.sol index 268fad3b5f..ca8a2f78e4 100644 --- a/protocol/test/foundry/sun/Cases.t.sol +++ b/protocol/test/foundry/sun/Cases.t.sol @@ -38,12 +38,12 @@ contract CasesTest is TestHelper { int256 deltaB; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); // Initialize well to balances. (1000 BEAN/ETH) addLiquidityToWell(well, 10000e6, 10 ether); - // call well to wsteth/bean to initalize the well. + // call well to wsteth/bean to initialize the well. // avoids errors due to gas limits. addLiquidityToWell(BEAN_WSTETH_WELL, 10e6, .01 ether); } @@ -72,7 +72,7 @@ contract CasesTest is TestHelper { l2SR = (caseId / 36) % 4; // set beanstalk state based on parameters. - deltaB = season.setBeanstalkState(price, podRate, changeInSoilDemand, l2SR, well); + deltaB = bs.setBeanstalkState(price, podRate, changeInSoilDemand, l2SR, well); // evaluate and update state. vm.expectEmit(true, true, false, false); @@ -80,7 +80,7 @@ contract CasesTest is TestHelper { vm.expectEmit(true, true, false, false); emit BeanToMaxLpGpPerBdvRatioChange(1, caseId, 0); - uint256 caseId = season.mockCalcCaseIdandUpdate(deltaB); + uint256 caseId = bs.mockCalcCaseIdandUpdate(deltaB); (, int8 bT, , int80 bL) = bs.getChangeFromCaseId(caseId); // CASE INVARIENTS @@ -157,8 +157,8 @@ contract CasesTest is TestHelper { function testSowTimeSoldOutSlower(uint256 lastSowTime, uint256 thisSowTime) public { // set podrate to reasonably high, // as we want to verify temp changes as a function of soil demand. - season.setPodRate(RES_HIGH); - season.setPrice(ABOVE_PEG, well); + bs.setPodRate(RES_HIGH); + bs.setPrice(ABOVE_PEG, well); // 10% temp for easier testing. bs.setMaxTempE(10); @@ -168,14 +168,14 @@ contract CasesTest is TestHelper { lastSowTime = bound(lastSowTime, 601, 3599); thisSowTime = bound(thisSowTime, lastSowTime + 61, 3660); - season.setLastSowTimeE(uint32(lastSowTime)); - season.setNextSowTimeE(uint32(thisSowTime)); + bs.setLastSowTimeE(uint32(lastSowTime)); + bs.setNextSowTimeE(uint32(thisSowTime)); // calc caseId - season.calcCaseIdE(1, 0); + bs.calcCaseIdE(1, 0); // beanstalk should record this season's sow time, - // and set it as last sow time for next season. + // and set it as last sow time for next bs. IMockFBeanstalk.Weather memory w = bs.weather(); assertEq(uint256(w.lastSowTime), thisSowTime); assertEq(uint256(w.thisSowTime), type(uint32).max); @@ -192,8 +192,8 @@ contract CasesTest is TestHelper { function testSowTimeSoldOutSowSameTime(uint256 lastSowTime, uint256 thisSowTime) public { // set podrate to reasonably high, // as we want to verify temp changes as a function of soil demand. - season.setPodRate(RES_HIGH); - season.setPrice(ABOVE_PEG, well); + bs.setPodRate(RES_HIGH); + bs.setPrice(ABOVE_PEG, well); // 10% temp for easier testing. bs.setMaxTempE(10); @@ -203,14 +203,14 @@ contract CasesTest is TestHelper { lastSowTime = bound(lastSowTime, 600, 3600); thisSowTime = bound(thisSowTime, lastSowTime, lastSowTime + 60); - season.setLastSowTimeE(uint32(lastSowTime)); - season.setNextSowTimeE(uint32(thisSowTime)); + bs.setLastSowTimeE(uint32(lastSowTime)); + bs.setNextSowTimeE(uint32(thisSowTime)); // calc caseId - season.calcCaseIdE(1, 0); + bs.calcCaseIdE(1, 0); // beanstalk should record this season's sow time, - // and set it as last sow time for next season. + // and set it as last sow time for next bs. IMockFBeanstalk.Weather memory w = bs.weather(); assertEq(uint256(w.lastSowTime), thisSowTime); assertEq(uint256(w.thisSowTime), type(uint32).max); @@ -227,8 +227,8 @@ contract CasesTest is TestHelper { function testSowTimeSoldOutFaster(uint256 lastSowTime, uint256 thisSowTime) public { // set podrate to reasonably high, // as we want to verify temp changes as a function of soil demand. - season.setPodRate(RES_HIGH); - season.setPrice(ABOVE_PEG, well); + bs.setPodRate(RES_HIGH); + bs.setPrice(ABOVE_PEG, well); // 10% temp for easier testing. bs.setMaxTempE(10); @@ -238,14 +238,14 @@ contract CasesTest is TestHelper { lastSowTime = bound(lastSowTime, 601, 3600); thisSowTime = bound(thisSowTime, 1, lastSowTime - 61); - season.setLastSowTimeE(uint32(lastSowTime)); - season.setNextSowTimeE(uint32(thisSowTime)); + bs.setLastSowTimeE(uint32(lastSowTime)); + bs.setNextSowTimeE(uint32(thisSowTime)); // calc caseId - season.calcCaseIdE(1, 0); + bs.calcCaseIdE(1, 0); // beanstalk should record this season's sow time, - // and set it as last sow time for next season. + // and set it as last sow time for next bs. IMockFBeanstalk.Weather memory w = bs.weather(); assertEq(uint256(w.lastSowTime), thisSowTime); assertEq(uint256(w.thisSowTime), type(uint32).max); diff --git a/protocol/test/foundry/sun/Flood.t.sol b/protocol/test/foundry/sun/Flood.t.sol index 20b0ba7698..e053ecee32 100644 --- a/protocol/test/foundry/sun/Flood.t.sol +++ b/protocol/test/foundry/sun/Flood.t.sol @@ -12,6 +12,7 @@ import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; import {Season} from "contracts/beanstalk/storage/System.sol"; import {Rain} from "contracts/beanstalk/storage/System.sol"; import {LibFlood} from "contracts/libraries/Silo/LibFlood.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; /** * @title FloodTest @@ -20,9 +21,8 @@ import {LibFlood} from "contracts/libraries/Silo/LibFlood.sol"; */ contract FloodTest is TestHelper { // Interfaces. - SeasonGettersFacet seasonGetters = SeasonGettersFacet(BEANSTALK); - MockFieldFacet field = MockFieldFacet(BEANSTALK); - SiloGettersFacet siloGetters = SiloGettersFacet(BEANSTALK); + SeasonGettersFacet seasonGetters; + SiloGettersFacet siloGetters; // test accounts address[] farmers; @@ -32,11 +32,14 @@ contract FloodTest is TestHelper { event SeasonOfPlentyField(uint256 toField); function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); + seasonGetters = SeasonGettersFacet(address(bs)); + siloGetters = SiloGettersFacet(address(bs)); + // init user. farmers.push(users[1]); vm.prank(farmers[0]); - bean.approve(BEANSTALK, type(uint256).max); + IBean(BEAN).approve(address(bs), type(uint256).max); // Initialize well to balances. (1000 BEAN/ETH) addLiquidityToWell( @@ -51,8 +54,8 @@ contract FloodTest is TestHelper { 10 ether // 10 ether. ); - season.siloSunrise(0); - season.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); depositStemBean = bs.stemTipForToken(BEAN); @@ -67,29 +70,29 @@ contract FloodTest is TestHelper { } function testBugReportLostPlenty2() public { - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[1], BEAN); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[1], BEAN); // set reserves so next season plenty is accrued setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); // 1st actual sop + bs.rainSunrise(); // 1st actual sop bs.mow(users[1], BEAN); - season.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); + bs.rainSunrise(); - season.droughtSunrise(); - season.droughtSunrise(); + bs.droughtSunrise(); + bs.droughtSunrise(); // withdraw deposit vm.prank(users[1]); bs.withdrawDeposit(BEAN, depositStemBean, 1_000e6, 0); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[1], BEAN); uint256 userPlenty = bs.balanceOfPlenty(users[1], BEAN_ETH_WELL); @@ -100,8 +103,8 @@ contract FloodTest is TestHelper { setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); + bs.rainSunrise(); bs.mow(users[1], BEAN); @@ -128,13 +131,13 @@ contract FloodTest is TestHelper { setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[3], BEAN); uint256 rainRoots = bs.balanceOfRainRoots(users[3]); assertEq(rainRoots, 500000000000000000000000000000000); - season.droughtSunrise(); + bs.droughtSunrise(); // withdraw vm.prank(users[3]); @@ -143,8 +146,8 @@ contract FloodTest is TestHelper { assertEq(rainRoots, 0); // start raining again - season.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); + bs.rainSunrise(); bs.mow(users[3], BEAN); rainRoots = bs.balanceOfRainRoots(users[3]); assertEq(rainRoots, 0); @@ -158,8 +161,8 @@ contract FloodTest is TestHelper { setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); // start raining - season.rainSunrise(); // sop + bs.rainSunrise(); // start raining + bs.rainSunrise(); // sop bs.mow(users[1], BEAN); @@ -181,8 +184,8 @@ contract FloodTest is TestHelper { setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); // start raining - season.rainSunrise(); // sop + bs.rainSunrise(); // start raining + bs.rainSunrise(); // sop bs.mow(users[1], BEAN); @@ -202,8 +205,8 @@ contract FloodTest is TestHelper { setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); // start raining - season.rainSunrise(); // sop + bs.rainSunrise(); // start raining + bs.rainSunrise(); // sop bs.mow(users[1], BEAN); @@ -216,8 +219,8 @@ contract FloodTest is TestHelper { bs.deposit(BEAN, 1_000e6, 0); // pass germination - season.siloSunrise(0); - season.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); // verify roots went up assertEq(bs.balanceOfRoots(users[1]), 20008000000000000000000000000000); @@ -236,16 +239,16 @@ contract FloodTest is TestHelper { function testGerminationRainRoots() public { bean.mint(users[3], 50_000e6); vm.prank(users[3]); - bean.approve(BEANSTALK, type(uint256).max); + IBean(BEAN).approve(address(bs), type(uint256).max); vm.prank(users[3]); bs.deposit(BEAN, 50_000e6, 0); setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); + bs.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[3], BEAN); uint256 totalRainRoots = bs.totalRainRoots(); @@ -260,20 +263,20 @@ contract FloodTest is TestHelper { function testSecondGerminationRainRoots() public { // not raining - season.rainSunrise(); // start raining + bs.rainSunrise(); // start raining uint256 totalRainRootsBefore = bs.totalRainRoots(); bean.mint(users[3], 50_000e6); vm.prank(users[3]); - bean.approve(BEANSTALK, type(uint256).max); + IBean(BEAN).approve(address(bs), type(uint256).max); vm.prank(users[3]); bs.deposit(BEAN, 50_000e6, 0); // set reserves so we'll sop setReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); setInstantaneousReserves(BEAN_ETH_WELL, 1000000e6, 1100e18); - season.rainSunrise(); // sop + bs.rainSunrise(); // sop bs.mow(users[3], BEAN); uint256 totalRainRootsAfter = bs.totalRainRoots(); @@ -306,7 +309,7 @@ contract FloodTest is TestHelper { function testRaining() public { // verify the beanToMaxLpGpPerBdvRatio is not zero before raining assertGt(bs.getBeanToMaxLpGpPerBdvRatio(), 0); - field.incrementTotalPodsE(1000e18, bs.activeField()); + bs.incrementTotalPodsE(1000e18, bs.activeField()); season.rainSunrise(); bs.mow(users[1], BEAN); @@ -328,11 +331,11 @@ contract FloodTest is TestHelper { } function testStopsRaining() public { - field.incrementTotalPodsE(1000e18, bs.activeField()); - season.rainSunrise(); + bs.incrementTotalPodsE(1000e18, bs.activeField()); + bs.rainSunrise(); bs.mow(users[1], BEAN); - season.droughtSunrise(); + bs.droughtSunrise(); bs.mow(users[1], BEAN); Season memory s = seasonGetters.time(); @@ -343,7 +346,7 @@ contract FloodTest is TestHelper { } function testSopsWhenAtPeg() public { - season.siloSunrise(25); + bs.siloSunrise(25); Season memory s = seasonGetters.time(); assertEq(s.lastSop, 0); @@ -352,7 +355,7 @@ contract FloodTest is TestHelper { function testSopsBelowPeg() public { setDeltaBforWell(-1000e6, BEAN_ETH_WELL, WETH); - season.siloSunrise(25); + bs.siloSunrise(25); Season memory s = seasonGetters.time(); assertEq(s.lastSop, 0); @@ -382,7 +385,7 @@ contract FloodTest is TestHelper { uint256 userCalcPlenty = (userCalcPlentyPerRoot * bs.balanceOfRoots(users[1])) / C.SOP_PRECISION; // 25595575914848452999 - season.rainSunrise(); + bs.rainSunrise(); // verify the beanToMaxLpGpPerBdvRatio is zero after rain starts assertEq(bs.getBeanToMaxLpGpPerBdvRatio(), 0); @@ -400,14 +403,14 @@ contract FloodTest is TestHelper { 51191151829696906017 ); - season.rainSunrise(); + bs.rainSunrise(); Season memory s = seasonGetters.time(); assertEq(s.lastSop, s.rainStart); assertEq(s.lastSopSeason, s.current); // check weth balance of beanstalk - assertEq(IERC20(WETH).balanceOf(BEANSTALK), 51191151829696906017); + assertEq(IERC20(WETH).balanceOf(address(bs)), 51191151829696906017); // after the swap, the composition of the pools are uint256[] memory balances = IWell(sopWell).getReserves(); assertEq(balances[0], 1048808848170); @@ -461,9 +464,8 @@ contract FloodTest is TestHelper { assertEq(bs.getBeanToMaxLpGpPerBdvRatio(), 0); bs.mow(users[2], BEAN); - season.rainSunrise(); - season.droughtSunrise(); - + bs.rainSunrise(); + bs.droughtSunrise(); setReserves(sopWell, 1048808848170, 1100e18); vm.expectEmit(); @@ -477,7 +479,7 @@ contract FloodTest is TestHelper { 25900501355272002583 ); - season.rainSunrises(2); + bs.rainSunrises(2); // sops p > 1 Season memory s = seasonGetters.time(); @@ -485,7 +487,7 @@ contract FloodTest is TestHelper { assertEq(s.lastSop, s.rainStart); assertEq(s.lastSopSeason, s.current); - assertEq(IERC20(WETH).balanceOf(BEANSTALK), 77091653184968908600); + assertEq(IERC20(WETH).balanceOf(address(bs)), 77091653184968908600); assertEq(reserves[0], 1074099498643); assertEq(reserves[1], 1074099498644727997417); @@ -526,7 +528,7 @@ contract FloodTest is TestHelper { // set instantaneous reserves differently setInstantaneousReserves(sopWell, 900_000e6, 1_100e18); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[2], sopWell); vm.expectEmit(); @@ -540,7 +542,7 @@ contract FloodTest is TestHelper { 51191151829696906017 ); - season.rainSunrise(); + bs.rainSunrise(); // end before each from hardhat test // sops p > 1 @@ -549,7 +551,7 @@ contract FloodTest is TestHelper { assertEq(s.lastSop, s.rainStart); assertEq(s.lastSopSeason, s.current); - assertEq(IERC20(WETH).balanceOf(BEANSTALK), 51191151829696906017); + assertEq(IERC20(WETH).balanceOf(address(bs)), 51191151829696906017); assertEq(reserves[0], 1048808848170); assertEq(reserves[1], 1048808848170303093983); @@ -729,7 +731,7 @@ contract FloodTest is TestHelper { uint256 userCalcPlenty = (userCalcPlentyPerRoot * bs.balanceOfRoots(users[1])) / C.SOP_PRECISION; // 25595575914848452999 - season.rainSunrise(); // start raining + bs.rainSunrise(); // start raining bs.mow(users[1], BEAN); vm.expectEmit(); @@ -740,10 +742,10 @@ contract FloodTest is TestHelper { 51191151829696906017 ); - season.rainSunrise(); // first sop + bs.rainSunrise(); // first sop // de-whitelist bean eth well - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.dewhitelistToken(BEAN_ETH_WELL); Season memory s = seasonGetters.time(); @@ -751,7 +753,7 @@ contract FloodTest is TestHelper { assertEq(s.lastSop, s.rainStart); assertEq(s.lastSopSeason, s.current); // check weth balance of beanstalk - assertEq(IERC20(WETH).balanceOf(BEANSTALK), 51191151829696906017); + assertEq(IERC20(WETH).balanceOf(address(bs)), 51191151829696906017); // after the swap, the composition of the pools are uint256[] memory balances = IWell(sopWell).getReserves(); assertEq(balances[0], 1048808848170); @@ -829,7 +831,7 @@ contract FloodTest is TestHelper { uint256 userCalcPlenty = (userCalcPlentyPerRoot * bs.balanceOfRoots(users[1])) / C.SOP_PRECISION; // 25595575914848452999 - season.rainSunrise(); // start raining + bs.rainSunrise(); // start raining bs.mow(users[1], BEAN); vm.expectEmit(); @@ -840,15 +842,15 @@ contract FloodTest is TestHelper { 51191151829696906017 ); - season.rainSunrise(); // first sop + bs.rainSunrise(); // first sop // de-whitelist bean eth well - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.dewhitelistToken(BEAN_ETH_WELL); // mow after dewhitelist bs.mow(users[1], BEAN); - season.rainSunrise(); // sop one more after dewhitelist + bs.rainSunrise(); // sop one more after dewhitelist // get balance of plenty bs.balanceOfPlenty(users[1], sopWell); @@ -856,14 +858,14 @@ contract FloodTest is TestHelper { setReserves(sopWell, 1_000_000e6, 900e18); // stop sopping - season.siloSunrise(0); - season.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); bs.mow(users[1], BEAN); setReserves(sopWell, 1_000_000e6, 1_100e18); // start sopping again - season.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); + bs.rainSunrise(); // neither of these should revert bs.mow(users[1], BEAN); @@ -883,14 +885,14 @@ contract FloodTest is TestHelper { uint256 initialPodLine = bs.podIndex(bs.activeField()); uint256 initialHarvestable = bs.totalHarvestableForActiveField(); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[1], BEAN); vm.expectEmit(); emit SeasonOfPlentyField(amount); - season.rainSunrise(); + bs.rainSunrise(); uint256 newHarvestable = bs.totalHarvestableForActiveField(); uint256 newBeanSupply = bean.totalSupply(); @@ -913,13 +915,13 @@ contract FloodTest is TestHelper { uint256 initialPodLine = bs.podIndex(bs.activeField()); uint256 initialHarvestable = bs.totalHarvestableForActiveField(); - season.rainSunrise(); + bs.rainSunrise(); bs.mow(users[1], BEAN); vm.expectEmit(); emit SeasonOfPlentyField(initialBeanSupply / 1000); - season.rainSunrise(); + bs.rainSunrise(); uint256 newHarvestable = bs.totalHarvestableForActiveField(); uint256 newBeanSupply = bean.totalSupply(); @@ -1038,19 +1040,19 @@ contract FloodTest is TestHelper { bean.mint(customUser, 10_000e6); vm.prank(customUser); - bean.approve(BEANSTALK, type(uint256).max); + IBean(BEAN).approve(address(bs), type(uint256).max); vm.prank(customUser); bs.deposit(BEAN, 1000e6, 0); - season.siloSunrise(0); - season.siloSunrise(0); - season.siloSunrise(0); // should be germinated by now, not mown though + bs.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); // should be germinated by now, not mown though address sopWell = BEAN_ETH_WELL; setReserves(sopWell, 1000000e6, 1100e18); - season.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); + bs.rainSunrise(); bs.mow(customUser, BEAN); @@ -1064,20 +1066,20 @@ contract FloodTest is TestHelper { bean.mint(customUser, 10_000e6); vm.prank(customUser); - bean.approve(BEANSTALK, type(uint256).max); + IBean(BEAN).approve(address(bs), type(uint256).max); vm.prank(customUser); bs.deposit(BEAN, 1000e6, 0); - season.siloSunrise(0); - season.siloSunrise(0); - season.siloSunrise(0); // should be germinated by now, not mown though + bs.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); // should be germinated by now, not mown though address sopWell = BEAN_ETH_WELL; setReserves(sopWell, 1000000e6, 1100e18); bs.mow(customUser, BEAN); - season.rainSunrise(); - season.rainSunrise(); + bs.rainSunrise(); + bs.rainSunrise(); bs.mow(customUser, BEAN); @@ -1127,14 +1129,14 @@ contract FloodTest is TestHelper { for (uint i = 0; i < users.length; i++) { bean.mint(users[i], beansMint); vm.prank(users[i]); - bean.approve(BEANSTALK, type(uint256).max); + IBean(BEAN).approve(address(bs), type(uint256).max); vm.prank(users[i]); bs.deposit(BEAN, beansDeposit, 0); } // pass germination process - season.siloSunrise(0); - season.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); if (mow) { for (uint i = 0; i < users.length; i++) { diff --git a/protocol/test/foundry/sun/Gauge.t.sol b/protocol/test/foundry/sun/Gauge.t.sol index 1978c72a04..074827d272 100644 --- a/protocol/test/foundry/sun/Gauge.t.sol +++ b/protocol/test/foundry/sun/Gauge.t.sol @@ -6,6 +6,8 @@ import {TestHelper, IMockFBeanstalk, MockToken, C, IWell} from "test/foundry/uti import {MockChainlinkAggregator} from "contracts/mocks/chainlink/MockChainlinkAggregator.sol"; import {MockLiquidityWeight} from "contracts/mocks/MockLiquidityWeight.sol"; import {GaugePointPrice} from "contracts/beanstalk/sun/GaugePoints/GaugePointPrice.sol"; +import {LibAppStorage, AppStorage} from "contracts/libraries/LibAppStorage.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; /** * @notice Tests the functionality of the gauge. @@ -15,18 +17,18 @@ contract GaugeTest is TestHelper { event BeanToMaxLpGpPerBdvRatioChange(uint256 indexed season, uint256 caseId, int80 absChange); // Interfaces. - MockLiquidityWeight mlw = MockLiquidityWeight(BEANSTALK); + MockLiquidityWeight mlw = MockLiquidityWeight(address(bs)); GaugePointPrice gpP; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); // deploy mockLiquidityWeight contract for testing. mlw = new MockLiquidityWeight(0.5e18); // deploy gaugePointPrice contract for WETH, with a price threshold of 500 USD, // and a gaugePoint of 10. - gpP = new GaugePointPrice(BEANSTALK, WETH, 500e6, 10e18); + gpP = new GaugePointPrice(address(bs), WETH, 500e6, 10e18); } ////////////////////// BEAN TO MAX LP RATIO ////////////////////// @@ -43,7 +45,7 @@ contract GaugeTest is TestHelper { if (foo < 3) caseId = caseId + (3 - foo); // set the bean to max lp to < 1 point. - season.setBeanToMaxLpGpPerBdvRatio(uint128(initBeanToMaxLPRatio)); + bs.setBeanToMaxLpGpPerBdvRatio(uint128(initBeanToMaxLPRatio)); // iterate through the sunrise with the case. vm.expectEmit(); @@ -52,7 +54,7 @@ contract GaugeTest is TestHelper { caseId, -int80(uint80(initBeanToMaxLPRatio)) ); - season.seedGaugeSunSunrise(0, caseId); + bs.seedGaugeSunSunrise(0, caseId); assertEq(bs.getBeanToMaxLpGpPerBdvRatio(), 0); } @@ -69,7 +71,7 @@ contract GaugeTest is TestHelper { if (foo > 2) caseId = caseId - foo; // set the bean to max lp to < 1 point. - season.setBeanToMaxLpGpPerBdvRatio(uint128(initBeanToMaxLPRatio)); + bs.setBeanToMaxLpGpPerBdvRatio(uint128(initBeanToMaxLPRatio)); // iterate through the sunrise with the case. vm.expectEmit(); @@ -78,7 +80,7 @@ contract GaugeTest is TestHelper { caseId, 100e18 - int80(uint80(initBeanToMaxLPRatio)) ); - season.seedGaugeSunSunrise(0, caseId); + bs.seedGaugeSunSunrise(0, caseId); assertEq(bs.getBeanToMaxLpGpPerBdvRatio(), 100e18); } @@ -203,7 +205,7 @@ contract GaugeTest is TestHelper { ////////////////////// AVERAGE GROWN STALK PER BDV PER SEASON ////////////////////// /** - * @notice verifies that the average grown stalk per season does not change if the season is less than the catchup season. + * @notice verifies that the average grown stalk per season does not change if the season is less than the catchup bs. */ function test_avgGrownStalkPerBdv_noChange(uint256 season) public { season = bound(season, 0, bs.getTargetSeasonsToCatchUp() - 1); @@ -225,7 +227,7 @@ contract GaugeTest is TestHelper { } /** - * @notice verifies that the average grown stalk per season changes after the catchup season. + * @notice verifies that the average grown stalk per season changes after the catchup bs. */ function test_avgGrownStalkPerBdv_changes(uint256 season) public { // season is capped to uint32 max - 1. @@ -373,7 +375,7 @@ contract GaugeTest is TestHelper { /** * When beanstalk has 1 LP token whitelisted, the gauge system adjusts * the bean and lp seeds based on: - * 1: the average grown stalk per bdv per season. + * 1: the average grown stalk per bdv per bs. * 2: the beanToMaxLpRatio. * @dev Given season < catchup season, the averageGrownStalkPerBdvPerSeason is static. See {test_avgGrownStalkPerBdv_changes} */ @@ -424,7 +426,7 @@ contract GaugeTest is TestHelper { assertApproxEqRel(calcBeanToLpRatio, targetRatio, 1e12); // verify that the seeds were properly calculated. - // bean bdv * bean seeds + LP bdv * LP seeds = total stalk Issued this season. + // bean bdv * bean seeds + LP bdv * LP seeds = total stalk Issued this bs. // total Stalk issued = averageGrownStalkPerBdvPerSeason * total bdv. uint256 beanBDV = bs.getTotalDepositedBdv(BEAN); uint256 lpBDV = bs.getTotalDepositedBdv(wellToken); @@ -653,8 +655,8 @@ contract GaugeTest is TestHelper { uint256 beanAmount ) internal prank(users[0]) { MockToken(UNRIPE_BEAN).mint(users[0], unripeAmount); - bean.mint(users[0], beanAmount); - bean.approve(BEANSTALK, beanAmount); + IBean(BEAN).mint(users[0], beanAmount); + IBean(BEAN).approve(address(bs), beanAmount); bs.addUnderlying(UNRIPE_BEAN, beanAmount); } @@ -666,7 +668,7 @@ contract GaugeTest is TestHelper { address underlyingToken = bs.getUnderlyingToken(UNRIPE_LP); uint256 lpOut = addLiquidityToWell(underlyingToken, beanAmount, 20000 ether); - MockToken(underlyingToken).approve(BEANSTALK, type(uint256).max); + MockToken(underlyingToken).approve(address(bs), type(uint256).max); bs.addUnderlying(UNRIPE_LP, lpOut); } @@ -694,7 +696,7 @@ contract GaugeTest is TestHelper { wellToken = whitelistedWells[i]; continue; } - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.dewhitelistToken(whitelistedWells[i]); } @@ -721,7 +723,7 @@ contract GaugeTest is TestHelper { address[] memory whitelistedWells = bs.getWhitelistedWellLpTokens(); for (uint i; i < whitelistedWells.length; i++) { - vm.prank(BEANSTALK); + vm.prank(address(bs)); IMockFBeanstalk.Implementation memory gpImplementation = IMockFBeanstalk.Implementation( address(0), bs.gaugePointsNoChange.selector, @@ -763,7 +765,7 @@ contract GaugeTest is TestHelper { IMockFBeanstalk.EvaluationParameters memory seedGauge = bs.getEvaluationParameters(); // change settings - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateSeedGaugeSettings( IMockFBeanstalk.EvaluationParameters( uint256(0), diff --git a/protocol/test/foundry/sun/Germination.t.sol b/protocol/test/foundry/sun/Germination.t.sol index 0cd5c43822..9370f11eed 100644 --- a/protocol/test/foundry/sun/Germination.t.sol +++ b/protocol/test/foundry/sun/Germination.t.sol @@ -14,13 +14,14 @@ import {C} from "contracts/C.sol"; */ contract GerminationTest is TestHelper { // Interfaces. - MockSiloFacet silo = MockSiloFacet(BEANSTALK); + MockSiloFacet silo; // test accounts address[] farmers; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); + silo = MockSiloFacet(address(bs)); // mint 1000 beans to user 1 and user 2 (user 0 is the beanstalk deployer). farmers.push(users[1]); @@ -36,7 +37,7 @@ contract GerminationTest is TestHelper { */ function test_depositGerminates(uint256 amount) public { // deposits bean into the silo. - (amount, ) = setUpSiloDepositTest(amount, farmers); + (amount, ) = setUpSiloDeposits(amount, farmers); // verify new state of silo. checkSiloAndUser(users[1], 0, amount); @@ -48,10 +49,10 @@ contract GerminationTest is TestHelper { */ function test_depositsContGerminating(uint256 amount) public { // deposits bean into the silo. - (amount, ) = setUpSiloDepositTest(amount, farmers); + (amount, ) = setUpSiloDeposits(amount, farmers); // call sunrise. - season.siloSunrise(0); + bs.siloSunrise(0); // verify new state of silo. checkSiloAndUser(users[1], 0, amount); @@ -63,11 +64,11 @@ contract GerminationTest is TestHelper { */ function test_depositsEndGermination(uint256 amount) public { // deposits bean into the silo. - (amount, ) = setUpSiloDepositTest(amount, farmers); + (amount, ) = setUpSiloDeposits(amount, farmers); // call sunrise twice. - season.siloSunrise(0); - season.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); // verify new state of silo. checkSiloAndUser(users[1], amount, 0); @@ -81,7 +82,7 @@ contract GerminationTest is TestHelper { function test_withdrawGerminating(uint256 amount) public { // deposits bean into the silo. int96 stem; - (amount, stem) = setUpSiloDepositTest(amount, farmers); + (amount, stem) = setUpSiloDeposits(amount, farmers); // withdraw beans from silo from user 1 and 2. withdrawDepositForUsers(farmers, BEAN, stem, amount, LibTransfer.To.EXTERNAL); @@ -98,10 +99,10 @@ contract GerminationTest is TestHelper { function test_withdrawGerminatingCont(uint256 amount) public { // deposits bean into the silo. int96 stem; - (amount, stem) = setUpSiloDepositTest(amount, farmers); + (amount, stem) = setUpSiloDeposits(amount, farmers); // call sunrise. - season.siloSunrise(0); + bs.siloSunrise(0); // withdraw beans from silo from user 1 and 2. withdrawDepositForUsers(farmers, BEAN, stem, amount, LibTransfer.To.EXTERNAL); @@ -119,7 +120,7 @@ contract GerminationTest is TestHelper { function test_transferGerminating(uint256 amount) public { // deposits bean into the silo. int96 stem; - (amount, stem) = setUpSiloDepositTest(amount, farmers); + (amount, stem) = setUpSiloDeposits(amount, farmers); uint256 grownStalk = bs.balanceOfGrownStalk(users[1], BEAN); farmers.push(users[3]); @@ -139,8 +140,8 @@ contract GerminationTest is TestHelper { function test_transferGerminatingCont(uint256 amount) public { // deposits bean into the silo. int96 stem; - (amount, stem) = setUpSiloDepositTest(amount, farmers); - season.siloSunrise(0); + (amount, stem) = setUpSiloDeposits(amount, farmers); + bs.siloSunrise(0); farmers.push(users[3]); farmers.push(users[4]); @@ -163,7 +164,7 @@ contract GerminationTest is TestHelper { uint256 _amount = initZeroEarnedBeansTest(amount, farmers, users[3]); // calls sunrise with some beans issued. - season.siloSunrise(sunriseBeans); + bs.siloSunrise(sunriseBeans); // verify silo/farmer states. Check user has no earned beans. assertEq(bs.totalStalk(), (2 * _amount + sunriseBeans) * C.STALK_PER_BEAN, "TotalStalk"); @@ -180,7 +181,7 @@ contract GerminationTest is TestHelper { // uint256 _amount = initZeroEarnedBeansTest(amount, farmers, users[3]); // // calls sunrise with some beans issued. - // season.siloSunrise(sunriseBeans); + // bs.siloSunrise(sunriseBeans); // // verify silo/farmer states. Check user has no earned beans. // // assertEq(bs.totalStalk(), (2 * _amount + sunriseBeans) * C.STALK_PER_BEAN, "TotalStalk0"); @@ -190,7 +191,7 @@ contract GerminationTest is TestHelper { // // assertEq(bs.totalRoots(), 2 * _amount * C.STALK_PER_BEAN * C.getRootsBase(), "TotalRoots"); // // calls sunrise (and finishes germination for user 3): - // season.siloSunrise(_sunriseBeans); + // bs.siloSunrise(_sunriseBeans); // // verify silo/farmer states. Check user has no earned beans. // // assertEq(bs.totalStalk(), (3 * _amount + 2 * sunriseBeans) * C.STALK_PER_BEAN, "TotalStalk1"); @@ -199,7 +200,7 @@ contract GerminationTest is TestHelper { // // assertEq(bs.getTotalDepositedBdv(BEAN), (3 * _amount + 2 * sunriseBeans), "TotalDepositedBdv"); // // assertEq(bs.totalRoots(), 5 * _amount * C.STALK_PER_BEAN * C.getRootsBase() / 2, "TotalRoots"); - // season.siloSunrise(0); + // bs.siloSunrise(0); // // verify silo/farmer states. Check user has no earned beans. // // assertEq(bs.totalStalk(), (3 * _amount + 2 * sunriseBeans) * C.STALK_PER_BEAN, "TotalStalk2"); @@ -259,11 +260,11 @@ contract GerminationTest is TestHelper { address newFarmer ) public returns (uint256 _amount) { // deposit 'amount' beans to the silo. - (_amount, ) = setUpSiloDepositTest(amount, initalFarmers); + (_amount, ) = setUpSiloDeposits(amount, initalFarmers); // call sunrise twice to finish the germination process. - season.siloSunrise(0); - season.siloSunrise(0); + bs.siloSunrise(0); + bs.siloSunrise(0); address[] memory farmer = new address[](1); farmer[0] = newFarmer; @@ -271,7 +272,7 @@ contract GerminationTest is TestHelper { mintTokensToUsers(farmer, BEAN, MAX_DEPOSIT_BOUND); // deposit into the silo. - setUpSiloDepositTest(amount, farmer); + setUpSiloDeposits(amount, farmer); } ////// ASSERTIONS ////// diff --git a/protocol/test/foundry/sun/Oracle.t.sol b/protocol/test/foundry/sun/Oracle.t.sol index 918a370d14..a4d0d2b0b9 100644 --- a/protocol/test/foundry/sun/Oracle.t.sol +++ b/protocol/test/foundry/sun/Oracle.t.sol @@ -39,7 +39,7 @@ contract OracleTest is TestHelper { address[] lps; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); farmers.push(users[1]); // add liquidity for the bean weth well, and bean wsteth well. @@ -64,7 +64,7 @@ contract OracleTest is TestHelper { // upon the first sunrise call of a well, the well cumulative reserves are initialized, // and will not return a deltaB. We initialize the well cumulative reserves here. // See: {LibWellMinting.capture} - season.initOracleForAllWhitelistedWells(); + bs.initOracleForAllWhitelistedWells(); // chainlink oracles need to be initialized for the wells. initializeChainlinkOraclesForWhitelistedWells(); @@ -91,7 +91,7 @@ contract OracleTest is TestHelper { vm.expectEmit(); emit WellOracle(currentSeason, lps[i], deltaB, data); } - season.captureE(); + bs.captureE(); } /** @@ -112,12 +112,12 @@ contract OracleTest is TestHelper { ); // deltaB may differ by 1 due to rounding errors. // validate poolDeltaB with calculated deltaB. - int256 poolDeltaB = season.getPoolDeltaBWithoutCap(lps[i]); + int256 poolDeltaB = bs.getPoolDeltaBWithoutCap(lps[i]); assertApproxEqAbs(poolDeltaB, deltaBPerWell[i], 1); vm.expectEmit(); emit WellOracle(currentSeason, lps[i], poolDeltaB, data); } - season.captureE(); + bs.captureE(); } /** diff --git a/protocol/test/foundry/sun/Sun.t.sol b/protocol/test/foundry/sun/Sun.t.sol index 6785b1efc0..48a6773c53 100644 --- a/protocol/test/foundry/sun/Sun.t.sol +++ b/protocol/test/foundry/sun/Sun.t.sol @@ -8,6 +8,7 @@ import {MockPump} from "contracts/mocks/well/MockPump.sol"; import {IWell, IERC20, Call} from "contracts/interfaces/basin/IWell.sol"; import {LibWhitelistedTokens} from "contracts/libraries/Silo/LibWhitelistedTokens.sol"; import {LibWellMinting} from "contracts/libraries/Minting/LibWellMinting.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; /** * @notice Tests the functionality of the sun, the distrubution of beans and soil. @@ -20,7 +21,7 @@ contract SunTest is TestHelper { // event Receipt(IBS.ShipmentRecipient indexed recipient, uint256 receivedAmount, bytes data); function setUp() public { - initializeBeanstalkTestState(true, true); + initializeBeanstalkTestState(true, false, true); } /** @@ -29,7 +30,7 @@ contract SunTest is TestHelper { */ function test_sunOnlySilo(int256 deltaB, uint256 caseId) public { uint32 currentSeason = bs.season(); - uint256 initialBeanBalance = bean.balanceOf(BEANSTALK); + uint256 initialBeanBalance = IBean(BEAN).balanceOf(address(bs)); uint256 initalPods = bs.totalUnharvestable(0); // cases can only range between 0 and 143. caseId = bound(caseId, 0, 143); @@ -50,7 +51,7 @@ contract SunTest is TestHelper { vm.expectEmit(); emit Soil(currentSeason + 1, soilIssued); - season.sunSunrise(deltaB, caseId); + bs.sunSunrise(deltaB, caseId); // if deltaB is positive, // 1) beans are minted equal to deltaB. @@ -58,13 +59,17 @@ contract SunTest is TestHelper { // needed to equal the newly paid off pods (scaled up or down). // 3) no pods should be paid off. if (deltaB >= 0) { - assertEq(bean.balanceOf(BEANSTALK), uint256(deltaB), "invalid bean minted +deltaB"); + assertEq( + IBean(BEAN).balanceOf(address(bs)), + uint256(deltaB), + "invalid bean minted +deltaB" + ); } // if deltaB is negative, soil is issued equal to deltaB. // no beans should be minted. if (deltaB <= 0) { assertEq( - initialBeanBalance - bean.balanceOf(BEANSTALK), + initialBeanBalance - IBean(BEAN).balanceOf(address(bs)), 0, "invalid bean minted -deltaB" ); @@ -87,7 +92,7 @@ contract SunTest is TestHelper { setRoutes_siloAndFields(); uint32 currentSeason = bs.season(); - uint256 initialBeanBalance = bean.balanceOf(BEANSTALK); + uint256 initialBeanBalance = IBean(BEAN).balanceOf(address(bs)); // cases can only range between 0 and 143. caseId = bound(caseId, 0, 143); // deltaB cannot exceed uint128 max. @@ -112,7 +117,7 @@ contract SunTest is TestHelper { vm.expectEmit(); emit Soil(currentSeason + 1, soilIssued); - season.sunSunrise(deltaB, caseId); + bs.sunSunrise(deltaB, caseId); // if deltaB is positive, // 1) beans are minted equal to deltaB. @@ -120,7 +125,11 @@ contract SunTest is TestHelper { // needed to equal the newly paid off pods (scaled up or down). // 3) totalunharvestable() should decrease by the amount issued to the field. if (deltaB >= 0) { - assertEq(bean.balanceOf(BEANSTALK), uint256(deltaB), "invalid bean minted +deltaB"); + assertEq( + IBean(BEAN).balanceOf(address(bs)), + uint256(deltaB), + "invalid bean minted +deltaB" + ); assertEq(bs.totalSoil(), soilIssued, "invalid soil @ +deltaB"); assertEq( bs.totalUnharvestable(0), @@ -132,7 +141,7 @@ contract SunTest is TestHelper { // no beans should be minted. if (deltaB <= 0) { assertEq( - initialBeanBalance - bean.balanceOf(BEANSTALK), + initialBeanBalance - IBean(BEAN).balanceOf(address(bs)), 0, "invalid bean minted -deltaB" ); @@ -178,14 +187,14 @@ contract SunTest is TestHelper { assertEq(sproutsInBarn, bs.totalUnfertilizedBeans(), "invalid sprouts in barn"); // bean supply may change due to fert issuance, and initial supply is placed here. - uint256 beansInBeanstalk = bean.balanceOf(BEANSTALK); + uint256 beansInBeanstalk = IBean(BEAN).balanceOf(address(bs)); int256 initialLeftoverBeans = int256(bs.leftoverBeans()); vm.expectEmit(true, false, false, false); emit Soil(currentSeason + 1, 0); - season.sunSunrise(deltaB, caseId); + bs.sunSunrise(deltaB, caseId); // if deltaB is positive, // 1) beans are minted equal to deltaB. @@ -195,7 +204,7 @@ contract SunTest is TestHelper { // 4) totalUnfertilizedBeans() should decrease by the amount issued to the barn. if (deltaB >= 0) { assertEq( - bean.balanceOf(BEANSTALK) - beansInBeanstalk, + IBean(BEAN).balanceOf(address(bs)) - beansInBeanstalk, uint256(deltaB), "invalid bean minted +deltaB" ); @@ -233,7 +242,7 @@ contract SunTest is TestHelper { // no beans should be minted. if (deltaB <= 0) { assertEq( - bean.balanceOf(BEANSTALK) - beansInBeanstalk, + IBean(BEAN).balanceOf(address(bs)) - beansInBeanstalk, 0, "invalid bean minted -deltaB" ); @@ -305,7 +314,7 @@ contract SunTest is TestHelper { // May change at each sunrise. uint256 priorEarnedBeans = bs.totalEarnedBeans(); - uint256 priorBeansInBeanstalk = bean.balanceOf(BEANSTALK); + uint256 priorBeansInBeanstalk = IBean(BEAN).balanceOf(address(bs)); uint256 priorUnfertilizedBeans = bs.totalUnfertilizedBeans(); uint256 priorLeftoverBeans = bs.leftoverBeans(); @@ -314,7 +323,7 @@ contract SunTest is TestHelper { // vm.expectEmit(false, false, false, false); // emit Soil(0, 0); - season.sunSunrise(deltaB, caseId); + bs.sunSunrise(deltaB, caseId); // if deltaB is positive, // 1) beans are minted equal to deltaB. @@ -324,7 +333,7 @@ contract SunTest is TestHelper { // 4) totalUnfertilizedBeans() should decrease by the amount issued to the barn. if (deltaB >= 0) { assertEq( - bean.balanceOf(BEANSTALK) - priorBeansInBeanstalk, + IBean(BEAN).balanceOf(address(bs)) - priorBeansInBeanstalk, uint256(deltaB), "invalid bean minted +deltaB" ); @@ -401,7 +410,7 @@ contract SunTest is TestHelper { // no beans should be minted. if (deltaB <= 0) { assertEq( - bean.balanceOf(BEANSTALK) - priorBeansInBeanstalk, + IBean(BEAN).balanceOf(address(bs)) - priorBeansInBeanstalk, 0, "invalid bean minted -deltaB" ); diff --git a/protocol/test/foundry/sun/Sunrise.t.sol b/protocol/test/foundry/sun/Sunrise.t.sol index 7d3e17b223..00c08cff5c 100644 --- a/protocol/test/foundry/sun/Sunrise.t.sol +++ b/protocol/test/foundry/sun/Sunrise.t.sol @@ -49,7 +49,7 @@ contract SunriseTest is TestHelper { uint256 constant SEASON_DURATION = 3600; function setUp() public { - initializeBeanstalkTestState(true, false); + initializeBeanstalkTestState(true, false, false); farmers.push(users[1]); // add liquidity for the bean weth well, and bean wsteth well. @@ -79,7 +79,7 @@ contract SunriseTest is TestHelper { // upon the first sunrise call of a well, the well cumulative reserves are initialized, // and will not return a deltaB. We initialize the well cumulative reserves here. // See: {LibWellMinting.capture} - season.initOracleForAllWhitelistedWells(); + bs.initOracleForAllWhitelistedWells(); // chainlink oracles need to be initialized for the wells. initializeChainlinkOraclesForWhitelistedWells(); @@ -94,9 +94,9 @@ contract SunriseTest is TestHelper { s = bound(s, 1, type(uint32).max); timestamp = bound(timestamp, 0, (s * SEASON_DURATION) - 1); skip(timestamp); - season.setCurrentSeasonE(uint32(s)); + bs.setCurrentSeasonE(uint32(s)); vm.expectRevert("Season: Still current Season."); - season.sunrise(); + bs.sunrise(); } /////////// VERIFY SUNRISE EXECUTION /////////// @@ -139,7 +139,7 @@ contract SunriseTest is TestHelper { function test_lateSunrise(uint256 s, uint256 secondsLate) public { // max season is type(uint32).max - 2. s = bound(s, 1, type(uint32).max - 2); - season.setCurrentSeasonE(uint32(s)); + bs.setCurrentSeasonE(uint32(s)); warpToNextSeasonTimestamp(); uint256 maxTimestamp = (type(uint32).max * SEASON_DURATION + INITIAL_TIMESTAMP); @@ -155,7 +155,7 @@ contract SunriseTest is TestHelper { function test_multiple_sunrises(uint256 s, uint256 secondsLate) public { // max season is type(uint32).max - 2. s = bound(s, 1, type(uint32).max - 3); - season.setCurrentSeasonE(uint32(s)); + bs.setCurrentSeasonE(uint32(s)); warpToNextSeasonTimestamp(); uint256 maxTimestamp = (type(uint32).max * SEASON_DURATION + INITIAL_TIMESTAMP - 7200); @@ -178,11 +178,11 @@ contract SunriseTest is TestHelper { */ function test_stepSeason(uint256 s) public { s = bound(s, 1, type(uint32).max - 1); - season.setCurrentSeasonE(uint32(s)); + bs.setCurrentSeasonE(uint32(s)); vm.expectEmit(); emit Sunrise(s + 1); - season.mockStepSeason(); + bs.mockStepSeason(); assertEq(bs.season(), s + 1); assertEq(bs.sunriseBlock(), block.number); @@ -251,7 +251,7 @@ contract SunriseTest is TestHelper { vm.resumeGasMetering(); - season.sunrise(); + bs.sunrise(); } /** diff --git a/protocol/test/foundry/upgrade.t.sol b/protocol/test/foundry/upgrade.t.sol index 9a8bd7ee24..a27a298689 100644 --- a/protocol/test/foundry/upgrade.t.sol +++ b/protocol/test/foundry/upgrade.t.sol @@ -3,11 +3,13 @@ pragma solidity >=0.6.0 <0.9.0; pragma abicoder v2; import {C} from "contracts/C.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; import {MockUpgradeFacet} from "contracts/mocks/mockFacets/MockUpgradeFacet.sol"; import {InitMint} from "contracts/beanstalk/init/InitMint.sol"; import {TestHelper} from "test/foundry/utils/TestHelper.sol"; import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; import {IDiamondCut} from "contracts/interfaces/IDiamondCut.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; /** * @title UpgradeDiamond @@ -39,8 +41,8 @@ contract UpgradeDiamond is TestHelper { // upgrade beanstalk with a new mock facet and init Mint. upgradeWithNewFacets( - BEANSTALK, // upgrading beanstalk. - IMockFBeanstalk(BEANSTALK).owner(), // fetch beanstalk owner. + LibConstant.BEANSTALK, // upgrading beanstalk. + IMockFBeanstalk(LibConstant.BEANSTALK).owner(), // fetch beanstalk owner. facetNames, newFacetAddresses, facetCutActions, @@ -50,26 +52,26 @@ contract UpgradeDiamond is TestHelper { ); } - function testWoohoo() public pure { + function testWoohoo() public view { // verify facet is added (call woohoo()). - assertEq(MockUpgradeFacet(BEANSTALK).woohoo(), 1); + assertEq(MockUpgradeFacet(LibConstant.BEANSTALK).woohoo(), 1); } function testInitMint() public view { // verify beans are minted. - assertEq(bean.balanceOf(users[0]), 100e6); + assertEq(IBean(BEAN).balanceOf(users[0]), 100e6); } function test_SelectorRemoval() public { // check woohoo. - assertEq(MockUpgradeFacet(BEANSTALK).woohoo(), 1); + assertEq(MockUpgradeFacet(LibConstant.BEANSTALK).woohoo(), 1); bytes4[] memory removeSelectors = new bytes4[](1); removeSelectors[0] = MockUpgradeFacet.woohoo.selector; // remove woohoo: upgradeWithNewFacets( - BEANSTALK, // upgrading beanstalk. - IMockFBeanstalk(BEANSTALK).owner(), // fetch beanstalk owner. + LibConstant.BEANSTALK, // upgrading beanstalk. + IMockFBeanstalk(LibConstant.BEANSTALK).owner(), // fetch beanstalk owner. new string[](0), new address[](0), new IDiamondCut.FacetCutAction[](0), @@ -80,6 +82,6 @@ contract UpgradeDiamond is TestHelper { // verify woohoo() is removed. vm.expectRevert(bytes("Diamond: Function does not exist")); - MockUpgradeFacet(BEANSTALK).woohoo(); + MockUpgradeFacet(LibConstant.BEANSTALK).woohoo(); } } diff --git a/protocol/test/foundry/utils/BasinDeployer.sol b/protocol/test/foundry/utils/BasinDeployer.sol index dc4208956a..f4d740ffb2 100644 --- a/protocol/test/foundry/utils/BasinDeployer.sol +++ b/protocol/test/foundry/utils/BasinDeployer.sol @@ -10,6 +10,7 @@ import {Utils, console} from "test/foundry/utils/Utils.sol"; import {Call, IAquifer} from "contracts/interfaces/basin/IAquifer.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {MockPump} from "contracts/mocks/well/MockPump.sol"; +import "forge-std/console.sol"; /** * @title BasinDeployer @@ -208,23 +209,18 @@ contract BasinDeployer is Utils { } function deployWBTCWellOnFork(bool mock, bool verbose) internal { - console.log("deploying wbtc well"); - - console.log("wellImplementations[0]:", wellImplementations[0]); - // deploy Bean WBTC well: // wells.push(deployBeanCp2Well([BEAN_WBTC_WELL, WBTC], _pump)); deployWellAtAddressNoData( BEAN_WBTC_WELL, BEAN, - WBTC, + L1_WBTC, wellFunctions[0], pumps[0], // multi flow pump wellImplementations[0] ); - console.log("deployed wbtc well"); if (verbose) console.log("Bean WBTC well deployed at:", wells[0]); vm.label(BEAN_WBTC_WELL, "BEAN/WBTC Well"); } diff --git a/protocol/test/foundry/utils/BeanstalkDeployer.sol b/protocol/test/foundry/utils/BeanstalkDeployer.sol index baf20fddfe..2a399c5ba7 100644 --- a/protocol/test/foundry/utils/BeanstalkDeployer.sol +++ b/protocol/test/foundry/utils/BeanstalkDeployer.sol @@ -10,7 +10,7 @@ import {Utils, console} from "test/foundry/utils/Utils.sol"; import {Diamond} from "contracts/beanstalk/Diamond.sol"; import {IDiamondCut} from "contracts/interfaces/IDiamondCut.sol"; import {MockInitDiamond} from "contracts/mocks/newMockInitDiamond.sol"; -import {InitDiamond} from "contracts/beanstalk/init/newInitDiamond.sol"; +import {InitDiamond} from "contracts/beanstalk/init/InitDiamond.sol"; import {DiamondLoupeFacet} from "contracts/beanstalk/diamond/DiamondLoupeFacet.sol"; /// Beanstalk Contracts w/external libraries. @@ -53,6 +53,9 @@ contract BeanstalkDeployer is Utils { "MarketplaceFacet", "ClaimFacet", "OracleFacet", + "L2MigrationFacet", + "TransmitOutFacet", + "TransmitInFacet", "L1ReceiverFacet", "GaugeGettersFacet" ]; @@ -80,11 +83,14 @@ contract BeanstalkDeployer is Utils { * @notice deploys the beanstalk diamond contract. * @param mock if true, deploys all mocks and sets the diamond address to the canonical beanstalk address. */ - function setupDiamond(bool mock, bool verbose) internal returns (Diamond d) { + function setupDiamond( + address payable diamondAddr, + bool mock, + bool verbose + ) public returns (Diamond d) { users = createUsers(6); deployer = users[0]; vm.label(deployer, "Deployer"); - vm.label(BEANSTALK, "Beanstalk"); // Create cuts. setupFacetAddresses(mock, true, false); @@ -94,7 +100,7 @@ contract BeanstalkDeployer is Utils { initialDeployFacetAddresses, cutActions ); - d = deployDiamondAtAddress(deployer, BEANSTALK); + d = deployDiamondAtAddress(deployer, diamondAddr); // if mocking, set the diamond address to // the canonical beanstalk address. diff --git a/protocol/test/foundry/utils/FertilizerDeployer.sol b/protocol/test/foundry/utils/FertilizerDeployer.sol index e8bc41cf37..526121d9f6 100644 --- a/protocol/test/foundry/utils/FertilizerDeployer.sol +++ b/protocol/test/foundry/utils/FertilizerDeployer.sol @@ -6,7 +6,7 @@ pragma abicoder v2; import {Utils, console} from "test/foundry/utils/Utils.sol"; import {Fertilizer} from "contracts/tokens/Fertilizer/Fertilizer.sol"; -import {C} from "contracts/C.sol"; +import {IFertilizer} from "contracts/interfaces/IFertilizer.sol"; interface IOwner { function transferOwnership(address newOwner) external; @@ -20,16 +20,18 @@ interface IOwner { * @notice Test helper contract to deploy Fertilizer. */ contract FertilizerDeployer is Utils { - address internal constant fertilizerAddress = 0x402c84De2Ce49aF88f5e2eF3710ff89bFED36cB6; + address FERTILIZER; + IFertilizer fertilizer; function initFertilizer(bool verbose) internal { - deployCodeTo("Fertilizer", fertilizerAddress); - if (verbose) console.log("Fertilizer deployed at: ", fertilizerAddress); + deployCodeTo("MockFertilizer", FERTILIZER); + if (verbose) console.log("MockFertilizer deployed at: ", FERTILIZER); + fertilizer = IFertilizer(FERTILIZER); } function transferFertilizerOwnership(address newOwner) internal { - vm.prank(IOwner(fertilizerAddress).owner()); - IOwner(fertilizerAddress).transferOwnership(newOwner); + vm.prank(IOwner(FERTILIZER).owner()); + IOwner(FERTILIZER).transferOwnership(newOwner); } function mintFertilizer() internal {} diff --git a/protocol/test/foundry/utils/LibAltC.sol b/protocol/test/foundry/utils/LibAltC.sol new file mode 100644 index 0000000000..114160d10f --- /dev/null +++ b/protocol/test/foundry/utils/LibAltC.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @title LibAltC + * @notice Contains alternative Beanstalk constants for use with a second Beanstalk ecosystem in testing. + * Only contains address constants that diverge from actual Beanstalk. + */ +library LibAltC { + address constant BEANSTALK = 0x00F84c1cF4Ca7fa8A8b0Dc923DA91ACA148B865C; + address internal constant BEAN = 0x006DD9acC7cDf83128C4aDF46847c301f94406ab; + + address internal constant FERTILIZER = 0x00d180156a2680F1e776b165080200cffaDa463d; + // address private constant FERTILIZER_ADMIN = ; + + address internal constant BEAN_ETH_WELL = 0x00c09d34D248ad13373193aA5Bc31876AfD577B5; + address internal constant BEAN_WSTETH_WELL = 0x00Ba43411e3Bd86a49500778021a1b101A18cB94; + + // address constant PRICE_DEPLOYER = ; + // address constant PRICE = ; +} diff --git a/protocol/test/foundry/utils/LibConstant.sol b/protocol/test/foundry/utils/LibConstant.sol index 94f8723ca1..294e8b6cdc 100644 --- a/protocol/test/foundry/utils/LibConstant.sol +++ b/protocol/test/foundry/utils/LibConstant.sol @@ -14,24 +14,32 @@ library LibConstant { address constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; address constant BEANSTALK = 0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5; address constant BEAN = 0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab; - address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + address constant L1_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address constant LUSD = 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0; address constant UNRIPE_BEAN = 0x1BEA0050E63e05FBb5D8BA2f10cf5800B6224449; address constant UNRIPE_LP = 0x1BEA3CcD22F4EBd3d37d731BA31Eeca95713716D; address constant FERTILIZER = 0x402c84De2Ce49aF88f5e2eF3710ff89bFED36cB6; address constant FERTILIZER_ADMIN = 0xfECB01359263C12Aa9eD838F878A596F0064aa6e; - address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; + address constant L1_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; address constant STABLE_FACTORY = 0xB9fC157394Af804a3578134A6585C0dc9cc990d4; address constant CRYPTO_FACTORY = 0x0959158b6040D32d04c301A72CBFD6b39E21c9AE; address constant BCM = 0xa9bA2C40b263843C04d344727b954A545c81D043; address constant USDC_MINTER = 0x5B6122C109B78C6755486966148C1D70a50A47D7; address constant SPOILED_BEAN = 0xDC59ac4FeFa32293A95889Dc396682858d52e5Db; - address constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + address constant WBTC = 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f; + address constant L1_WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address constant WSTETH = 0x5979D7b546E38E414F7E9822514be443A4800529; + address constant L1_WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address constant PUBLIUS = 0x925753106FCdB6D2f30C3db295328a0A1c5fD1D1; address constant PRICE_DEPLOYER = 0x884B463E078Ff26C4b83792dB9bEF33619a69767; address constant PRICE = 0xA57289161FF18D67A68841922264B317170b0b81; address constant BEANSTALK_FARMS = 0x21DE18B6A8f78eDe6D16C50A167f6B222DC08DF7; address constant BEAN_SPROUT = 0xb7ab3f0667eFF5e2299d39C23Aa0C956e8982235; + address constant BEAN_ETH_WELL = 0xBEA0e11282e2bB5893bEcE110cF199501e872bAd; + address constant BEAN_WSTETH_WELL = 0xBeA0000113B0d182f4064C86B71c315389E4715D; + address payable constant PIPELINE = payable(0xb1bE000644bD25996b0d9C2F7a6D6BA3954c91B0); } diff --git a/protocol/test/foundry/utils/OracleDeployer.sol b/protocol/test/foundry/utils/OracleDeployer.sol index 0a2b3e0e3c..a9ed23a08c 100644 --- a/protocol/test/foundry/utils/OracleDeployer.sol +++ b/protocol/test/foundry/utils/OracleDeployer.sol @@ -74,7 +74,7 @@ contract OracleDeployer is Utils { [WBTC_USDC_03_POOL, WBTC, USDC] // WBTC/USDC ]; - // oracles must be initalized at some price. Assumes index matching with pools. + // oracles must be initialized at some price. Assumes index matching with pools. uint256[][] public priceData = [[uint256(1e18), 18], [uint256(500e6), 8]]; // new custom oracle implmenetations should be added here. @@ -189,7 +189,7 @@ contract OracleDeployer is Utils { // init L1 ETH:USD oracle updateOracleImplementationForTokenUsingChainlinkAggregator( - L1_WETH, + WETH, ETH_USD_CHAINLINK_PRICE_AGGREGATOR, verbose ); @@ -214,7 +214,7 @@ contract OracleDeployer is Utils { abi.encode(FOUR_HOUR_TIMEOUT) ); - vm.prank(BEANSTALK); + vm.prank(address(bs)); bs.updateOracleImplementationForToken(token, oracleImplementation); if (verbose) @@ -228,7 +228,8 @@ contract OracleDeployer is Utils { ) internal { address _ethChainlinkOracle = ETH_USD_CHAINLINK_PRICE_AGGREGATOR; uint256 _ethTimeout = 3600 * 4; - vm.prank(BEANSTALK); + + vm.prank(address(bs)); bs.updateOracleImplementationForToken( token, IMockFBeanstalk.Implementation( diff --git a/protocol/test/foundry/utils/ShipmentDeployer.sol b/protocol/test/foundry/utils/ShipmentDeployer.sol index 9c975bc68a..8e472a2e0e 100644 --- a/protocol/test/foundry/utils/ShipmentDeployer.sol +++ b/protocol/test/foundry/utils/ShipmentDeployer.sol @@ -26,18 +26,18 @@ contract ShipmentDeployer is Utils { address shipmentPlanner; address shipmentPlannerFour; - function initShipping(bool verbose) internal { - bs = IBS(BEANSTALK); - - // Create Field, set active, and initialize Temperature. + function initShipping(bool verbose) public { + // Create two Fields, set active, and initialize Temperature. + // vm.prank(deployer); + // bs.addField(); vm.prank(deployer); bs.addField(); vm.prank(deployer); bs.setActiveField(0, 1); // Deploy the planner, which will determine points and caps of each route. - shipmentPlanner = address(new ShipmentPlanner(BEANSTALK)); - shipmentPlannerFour = address(new ShipmentPlannerFour(BEANSTALK)); + shipmentPlanner = address(new ShipmentPlanner(address(bs))); + shipmentPlannerFour = address(new ShipmentPlannerFour(address(bs))); // Set up three routes: the Silo, Barn, and a Field. setRoutes_siloAndBarnAndField(); @@ -48,7 +48,7 @@ contract ShipmentDeployer is Utils { /** * @notice Set the shipment routes to only the Silo. It will receive 100% of Mints. */ - function setRoutes_silo() internal { + function setRoutes_silo() public { IBS.ShipmentRoute[] memory shipmentRoutes = new IBS.ShipmentRoute[](1); shipmentRoutes[0] = IBS.ShipmentRoute({ planContract: shipmentPlanner, @@ -63,7 +63,7 @@ contract ShipmentDeployer is Utils { /** * @notice Set the shipment routes to the Silo and Barn. Each wil receive 50% of Mints. */ - function setRoutes_siloAndBarn() internal { + function setRoutes_siloAndBarn() public { IBS.ShipmentRoute[] memory shipmentRoutes = new IBS.ShipmentRoute[](2); shipmentRoutes[0] = IBS.ShipmentRoute({ planContract: shipmentPlanner, @@ -81,8 +81,8 @@ contract ShipmentDeployer is Utils { bs.setShipmentRoutes(shipmentRoutes); } - function setRoutes_siloAndFields() internal { - uint256 fieldCount = IBS(BEANSTALK).fieldCount(); + function setRoutes_siloAndFields() public { + uint256 fieldCount = IBS(address(bs)).fieldCount(); IBS.ShipmentRoute[] memory shipmentRoutes = new IBS.ShipmentRoute[](1 + fieldCount); shipmentRoutes[0] = IBS.ShipmentRoute({ planContract: shipmentPlanner, @@ -106,8 +106,8 @@ contract ShipmentDeployer is Utils { * @notice Set the shipment routes to the Silo, Barn, and 1 Field. Each will receive 1/3 of Mints. * @dev Need to add Fields before calling. */ - function setRoutes_siloAndBarnAndField() internal { - uint256 fieldCount = IBS(BEANSTALK).fieldCount(); + function setRoutes_siloAndBarnAndField() public { + uint256 fieldCount = IBS(address(bs)).fieldCount(); IBS.ShipmentRoute[] memory shipmentRoutes = new IBS.ShipmentRoute[](2 + fieldCount); shipmentRoutes[0] = IBS.ShipmentRoute({ planContract: shipmentPlanner, @@ -136,8 +136,8 @@ contract ShipmentDeployer is Utils { * Mints are split 3/3/3/1, respectively. * @dev Need to add Fields before calling. */ - function setRoutes_siloAndBarnAndTwoFields() internal { - uint256 fieldCount = IBS(BEANSTALK).fieldCount(); + function setRoutes_siloAndBarnAndTwoFields() public { + uint256 fieldCount = IBS(address(bs)).fieldCount(); require(fieldCount == 2, "Must have 2 Fields to set routes"); IBS.ShipmentRoute[] memory shipmentRoutes = new IBS.ShipmentRoute[](2 + fieldCount); shipmentRoutes[0] = IBS.ShipmentRoute({ diff --git a/protocol/test/foundry/utils/TestHelper.sol b/protocol/test/foundry/utils/TestHelper.sol index 3e0eb6be40..f29da150d8 100644 --- a/protocol/test/foundry/utils/TestHelper.sol +++ b/protocol/test/foundry/utils/TestHelper.sol @@ -18,10 +18,14 @@ import {DepotDeployer} from "test/foundry/utils/DepotDeployer.sol"; import {OracleDeployer} from "test/foundry/utils/OracleDeployer.sol"; import {FertilizerDeployer} from "test/foundry/utils/FertilizerDeployer.sol"; import {ShipmentDeployer} from "test/foundry/utils/ShipmentDeployer.sol"; +import {LibAltC} from "test/foundry/utils/LibAltC.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; +import {LibWell, IWell, IERC20} from "contracts/libraries/Well/LibWell.sol"; import {IWell, IERC20} from "contracts/interfaces/basin/IWell.sol"; import {C} from "contracts/C.sol"; import {LibAppStorage} from "contracts/libraries/LibAppStorage.sol"; import {AppStorage} from "contracts/beanstalk/storage/AppStorage.sol"; +import {IBean} from "contracts/interfaces/IBean.sol"; ///// COMMON IMPORTED LIBRARIES ////// import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol"; @@ -34,6 +38,9 @@ import {Pipeline} from "contracts/pipeline/Pipeline.sol"; * @title TestHelper * @author Brean * @notice Test helper contract for Beanstalk tests. + * + * This contract represents the initialization of a fresh Beanstalk system for testing. + * All deployment addresses for Beanstalk ecosystem contracts are set in this contract. */ contract TestHelper is Test, @@ -46,7 +53,7 @@ contract TestHelper is { Pipeline pipeline; - MockToken bean = MockToken(BEAN); + MockToken bean; MockSeasonFacet season = MockSeasonFacet(BEANSTALK); // ideally, timestamp should be set to 1_000_000. @@ -71,9 +78,21 @@ contract TestHelper is /** * @notice initializes the state of the beanstalk contracts for testing. */ - function initializeBeanstalkTestState(bool mock, bool verbose) public { + function initializeBeanstalkTestState(bool mock, bool alt, bool verbose) public { + bean = MockToken(LibConstant.BEAN); + FERTILIZER = LibConstant.FERTILIZER; + address payable bsAddr; + // Set the deployment addresses of various Beanstalk components. + if (!alt) { + bsAddr = payable(LibConstant.BEANSTALK); + vm.label(bsAddr, "Beanstalk_"); + } else { + bsAddr = payable(LibAltC.BEANSTALK); + vm.label(bsAddr, "Alt_Beanstalk"); + } + // general mock interface for beanstalk. - bs = IMockFBeanstalk(BEANSTALK); + bs = IMockFBeanstalk(bsAddr); // initialize misc contracts. initMisc(); @@ -82,7 +101,7 @@ contract TestHelper is // as starting from an timestamp of 0 can cause issues. vm.warp(INITIAL_TIMESTAMP); - // initalize mock tokens. + // initialize mock tokens. initMockTokens(verbose); // initialize Depot: @@ -100,10 +119,10 @@ contract TestHelper is // deploy fertilizer contract, and transfer ownership to beanstalk. // note: does not initailize barn raise. initFertilizer(verbose); - transferFertilizerOwnership(BEANSTALK); + transferFertilizerOwnership(bsAddr); - // initialize Diamond, initalize users: - setupDiamond(mock, verbose); + // initialize Diamond, initialize users: + setupDiamond(bsAddr, mock, verbose); // Initialize Shipment Routes and Plans. initShipping(verbose); @@ -120,7 +139,7 @@ contract TestHelper is address user, uint256 unripeBeanAmount, uint256 unripeLpAmount - ) internal { + ) public { // mint tokens to users. mintTokensToUser(user, UNRIPE_BEAN, unripeBeanAmount); mintTokensToUser(user, UNRIPE_LP, unripeLpAmount); @@ -167,7 +186,7 @@ contract TestHelper is function maxApproveBeanstalk(address[] memory users) public { for (uint i; i < users.length; i++) { vm.prank(users[i]); - bean.approve(BEANSTALK, type(uint256).max); + IBean(LibConstant.BEAN).approve(address(bs), type(uint256).max); } } @@ -188,14 +207,14 @@ contract TestHelper is function mintTokensToUser(address user, address token, uint256 amount) internal { MockToken(token).mint(user, amount); vm.prank(user); - MockToken(token).approve(BEANSTALK, type(uint256).max); + MockToken(token).approve(address(bs), type(uint256).max); } function addLiquidityToWell( address well, uint256 beanAmount, uint256 nonBeanTokenAmount - ) internal returns (uint256) { + ) public returns (uint256) { return addLiquidityToWell(users[0], well, beanAmount, nonBeanTokenAmount); } @@ -313,7 +332,7 @@ contract TestHelper is MockToken(token).mint(user, amount); } outputAmount = amount; - MockToken(token).approve(BEANSTALK, amount); + MockToken(token).approve(address(bs), amount); bs.deposit(token, amount, 0); } @@ -518,17 +537,17 @@ contract TestHelper is /** * @notice Set up the silo deposit test by depositing beans to the silo from multiple users. * @param amount The amount of beans to deposit. - * @return _amount The actual amount of beans deposited. + * @return amount The actual amount of beans deposited. * @return stem The stem tip for the deposited beans. */ - function setUpSiloDepositTest( + function setUpSiloDeposits( uint256 amount, - address[] memory _farmers - ) public returns (uint256 _amount, int96 stem) { - _amount = bound(amount, 1, MAX_DEPOSIT_BOUND); - - depositForUsers(_farmers, BEAN, _amount, LibTransfer.From.EXTERNAL); - stem = bs.stemTipForToken(BEAN); + address[] memory users + ) public returns (uint256, int96) { + amount = bound(amount, 1, MAX_DEPOSIT_BOUND); + depositForUsers(users, BEAN, amount, LibTransfer.From.EXTERNAL); + int96 stem = bs.stemTipForToken(BEAN); + return (amount, stem); } /** @@ -545,21 +564,30 @@ contract TestHelper is LibTransfer.From mode ) public { for (uint256 i = 0; i < users.length; i++) { + mintTokensToUser(users[i], BEAN, amount); vm.prank(users[i]); - bs.deposit(token, amount, uint8(mode)); // switching from silo.deposit to bs.deposit, but bs does not have a From enum, so casting to uint8. + bs.deposit(token, amount, uint8(mode)); } } /** - * @notice mints `sowAmount` beans for farmer, - * issues `sowAmount` of beans to farmer. - * sows `sowAmount` of beans. + * @notice Sows beans for a user. */ - function sowAmountForFarmer(address farmer, uint256 sowAmount) internal { - bs.setSoilE(sowAmount); - mintTokensToUser(farmer, BEAN, sowAmount); - vm.prank(farmer); - bs.sow(sowAmount, 0, uint8(LibTransfer.From.EXTERNAL)); + function sowForUser(address user, uint256 beans) internal returns (uint256 pods) { + bs.setSoilE(beans); + mintTokensToUser(user, BEAN, beans); + vm.prank(user); + return bs.sow(beans, 0, uint8(LibTransfer.From.EXTERNAL)); + } + + function buyFertForUser( + address user, + uint256 ethAmount + ) internal returns (uint256 fertilizerAmountOut) { + ethAmount = bound(ethAmount, 1e18, 100e18); + mintTokensToUser(user, bs.getBarnRaiseToken(), ethAmount); + vm.prank(user); + return bs.mintFertilizer(ethAmount, 0, 0); } /** @@ -567,7 +595,7 @@ contract TestHelper is * and warps the time to that timestamp. */ function warpToNextSeasonTimestamp() internal noGasMetering { - uint256 nextTimestamp = season.getNextSeasonStart(); + uint256 nextTimestamp = bs.getNextSeasonStart(); vm.warp(nextTimestamp); } diff --git a/protocol/test/foundry/utils/Utils.sol b/protocol/test/foundry/utils/Utils.sol index e71203c278..270fbde6d0 100644 --- a/protocol/test/foundry/utils/Utils.sol +++ b/protocol/test/foundry/utils/Utils.sol @@ -6,30 +6,34 @@ import "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; import {BeanstalkERC20} from "contracts/tokens/ERC20/BeanstalkERC20.sol"; +import {LibConstant} from "test/foundry/utils/LibConstant.sol"; /** * @dev common utilities for forge tests */ contract Utils is Test { + IMockFBeanstalk public bs; + // beanstalk address payable constant BEANSTALK = payable(address(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5)); - address internal constant BEAN = 0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab; - address internal constant UNRIPE_BEAN = 0x1BEA0050E63e05FBb5D8BA2f10cf5800B6224449; - address internal constant UNRIPE_LP = 0x1BEA3CcD22F4EBd3d37d731BA31Eeca95713716D; - address internal constant L1_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - address internal constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; - address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; - address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; - address internal constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; - address internal constant BEAN_ETH_WELL = 0xBEA0e11282e2bB5893bEcE110cF199501e872bAd; - address internal constant BEAN_WSTETH_WELL = 0xBeA0000113B0d182f4064C86B71c315389E4715D; - address payable internal constant PIPELINE = - payable(0xb1bE000644bD25996b0d9C2F7a6D6BA3954c91B0); + address internal constant BEAN = LibConstant.BEAN; + address internal constant UNRIPE_BEAN = LibConstant.UNRIPE_BEAN; + address internal constant UNRIPE_LP = LibConstant.UNRIPE_LP; + address internal constant WETH = LibConstant.WETH; // NOTE: setting L1 weth to weth here for tests is intentional + address internal constant L1_WETH = LibConstant.L1_WETH; + address internal constant WSTETH = LibConstant.WSTETH; + address internal constant L1_WSTETH = LibConstant.L1_WSTETH; + address internal constant USDC = LibConstant.USDC; + address internal constant L1_USDC = LibConstant.L1_USDC; + address internal constant USDT = LibConstant.USDT; + address internal constant WBTC = LibConstant.WBTC; + address internal constant L1_WBTC = LibConstant.L1_WBTC; + address internal constant BEAN_ETH_WELL = LibConstant.BEAN_ETH_WELL; + address internal constant BEAN_WSTETH_WELL = LibConstant.BEAN_WSTETH_WELL; + address payable internal constant PIPELINE = payable(LibConstant.PIPELINE); - IMockFBeanstalk bs; address internal deployer; using Strings for uint256; diff --git a/protocol/test/hardhat/Silo.test.js b/protocol/test/hardhat/Silo.test.js index 191d2afd56..005fdd9971 100644 --- a/protocol/test/hardhat/Silo.test.js +++ b/protocol/test/hardhat/Silo.test.js @@ -21,7 +21,7 @@ describe("newSilo", function () { // `mockBeanstalk` has functions that are only available in the mockFacets. [beanstalk, mockBeanstalk] = await getAllBeanstalkContracts(contracts.beanstalkDiamond.address); - // initalize users - mint bean and approve beanstalk to use all beans. + // initialize users - mint bean and approve beanstalk to use all beans. await initializeUsersForToken(BEAN, [user, user2, user3, user4], to6("10000")); // deposit 1000 beans from 2 users. diff --git a/protocol/utils/well.js b/protocol/utils/well.js index c773600337..e54f27b802 100644 --- a/protocol/utils/well.js +++ b/protocol/utils/well.js @@ -348,7 +348,7 @@ async function deployMockWellWithMockPump(address = BEAN_ETH_WELL, token1 = WETH await ethers.getContractFactory("MockSetComponentsWell", await getWellDeployer()) ).deploy(); await well.deployed(); - // if address is uninitalized, do not set code. + // if address is uninitialized, do not set code. if (address != ZERO_ADDRESS) { await network.provider.send("hardhat_setCode", [ address,