From 17deddcf859b1f46f3513493cf20125fe06f207f Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 22 Mar 2024 12:35:01 +0200 Subject: [PATCH 01/86] Import soil fix changed --- protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index b46ac438a9..b6f39ddb5d 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -237,7 +237,7 @@ contract Sun is Oracle { function setSoilBelowPeg(int256 twaDeltaB) internal { // Calculate deltaB from instantaneous reserves. - // NOTE: deltaB is calculated only from the Bean:ETH Well at this time + // NOTE: deltaB is calculated only from the Bean:ETH Well at this time. // If more wells are added, this will need to be updated. (int256 instDeltaB, ,) = LibWellMinting.instantaneousDeltaB(C.BEAN_ETH_WELL); From 68e9ebd6fb0e574885996ff20357c4e38df4b9f9 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 22 Mar 2024 13:11:54 +0200 Subject: [PATCH 02/86] Import fert metadata changes --- .../beanstalk/barn/FertilizerFacet.sol | 2 + .../contracts/beanstalk/init/InitReplant.sol | 4 +- protocol/contracts/interfaces/IFertilizer.sol | 1 - .../contracts/libraries/LibFertilizer.sol | 55 ++++++++ protocol/contracts/libraries/LibStrings.sol | 46 +++++++ .../mocks/mockFacets/MockFertilizerFacet.sol | 4 + .../tokens/Fertilizer/Fertilizer.sol | 58 ++++++++ .../tokens/Fertilizer/FertilizerImage.sol | 115 ++++++++++++++++ .../tokens/Fertilizer/Internalizer.sol | 74 ++++++++++- protocol/test/Fertilizer.test.js | 125 ++++++++++++++++++ 10 files changed, 475 insertions(+), 9 deletions(-) create mode 100644 protocol/contracts/tokens/Fertilizer/FertilizerImage.sol diff --git a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol index 4ede65027b..e21d4eb721 100644 --- a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol +++ b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol @@ -119,6 +119,8 @@ contract FertilizerFacet { ); } + ///////////////////////////// Fertilizer Getters ////////////////////////////// + function totalFertilizedBeans() external view returns (uint256 beans) { return s.fertilizedIndex; } diff --git a/protocol/contracts/beanstalk/init/InitReplant.sol b/protocol/contracts/beanstalk/init/InitReplant.sol index 60e94fc749..127ae00a19 100644 --- a/protocol/contracts/beanstalk/init/InitReplant.sol +++ b/protocol/contracts/beanstalk/init/InitReplant.sol @@ -32,6 +32,8 @@ contract InitReplant { C.fertilizerAddress(), fertilizerImplementation ); - C.fertilizer().setURI('https://fert.bean.money/'); + // The setURI function is removed because of the + // fertilizer on-chain metadata update. + // C.fertilizer().setURI('https://fert.bean.money/'); } } \ No newline at end of file diff --git a/protocol/contracts/interfaces/IFertilizer.sol b/protocol/contracts/interfaces/IFertilizer.sol index 718560c680..aa233e44cf 100644 --- a/protocol/contracts/interfaces/IFertilizer.sol +++ b/protocol/contracts/interfaces/IFertilizer.sol @@ -17,5 +17,4 @@ interface IFertilizer { function balanceOfUnfertilized(address account, uint256[] memory ids) external view returns (uint256); function lastBalanceOf(address account, uint256 id) external view returns (Balance memory); function lastBalanceOfBatch(address[] memory account, uint256[] memory id) external view returns (Balance[] memory); - function setURI(string calldata newuri) external; } \ No newline at end of file diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 2543199961..9067a26d0f 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -40,6 +40,14 @@ library LibFertilizer { uint128 private constant RESTART_HUMIDITY = 2500; uint128 private constant END_DECREASE_SEASON = REPLANT_SEASON + 461; + /** + * @dev Adds a new fertilizer to Beanstalk, updates global state, + * the season queue, and returns the corresponding fertilizer id. + * @param season The season the fertilizer is added. + * @param fertilizerAmount The amount of Fertilizer to add. + * @param minLP The minimum amount of LP to add. + * @return id The id of the Fertilizer. + */ function addFertilizer( uint128 season, uint256 tokenAmountIn, @@ -69,10 +77,24 @@ library LibFertilizer { emit SetFertilizer(id, bpf); } + /** + * @dev Calculates the Beans Per Fertilizer for a given season. + * Forluma is bpf = Humidity + 1000 * 1,000 + * @param id The id of the Fertilizer. + * @return bpf The Beans Per Fertilizer. + */ function getBpf(uint128 id) internal pure returns (uint128 bpf) { bpf = getHumidity(id).add(1000).mul(PADDING); } + /** + * @dev Calculates the Humidity for a given season. + * The Humidity was 500% prior to Replant, after which it dropped to 250% (Season 6074) + * and then decreased by an additional 0.5% each Season until it reached 20%. + * The Humidity will remain at 20% until all Available Fertilizer is purchased. + * @param id The season. + * @return humidity The corresponding Humidity. + */ function getHumidity(uint128 id) internal pure returns (uint128 humidity) { if (id == 0) return 5000; if (id >= END_DECREASE_SEASON) return 200; @@ -150,6 +172,14 @@ library LibFertilizer { s.recapitalized = s.recapitalized.add(usdAmount); } + /** + * @dev Adds a fertilizer id in the queue. + * fFirst is the lowest active Fertilizer Id (see AppStorage) + * (start of linked list that is stored by nextFid). + * The highest active Fertilizer Id + * (end of linked list that is stored by nextFid). + * @param id The id of the fertilizer. + */ function push(uint128 id) internal { AppStorage storage s = LibAppStorage.diamondStorage(); if (s.fFirst == 0) { @@ -179,6 +209,10 @@ library LibFertilizer { } } + /** + * @dev Returns the dollar amount remaining for beanstalk to recapitalize. + * @return remaining The dollar amount remaining. + */ function remainingRecapitalization() internal view @@ -194,6 +228,12 @@ library LibFertilizer { return totalDollars.sub(s.recapitalized); } + /** + * @dev Removes the first fertilizer id in the queue. + * fFirst is the lowest active Fertilizer Id (see AppStorage) + * (start of linked list that is stored by nextFid). + * @return bool Whether the queue is empty. + */ function pop() internal returns (bool) { AppStorage storage s = LibAppStorage.diamondStorage(); uint128 first = s.fFirst; @@ -211,16 +251,31 @@ library LibFertilizer { return true; } + /** + * @dev Returns the amount (supply) of fertilizer for a given id. + * @param id The id of the fertilizer. + */ function getAmount(uint128 id) internal view returns (uint256) { AppStorage storage s = LibAppStorage.diamondStorage(); return s.fertilizer[id]; } + /** + * @dev Returns the next fertilizer id in the list given a fertilizer id. + * nextFid is a linked list of Fertilizer Ids ordered by Id number. (See AppStorage) + * @param id The id of the fertilizer. + */ function getNext(uint128 id) internal view returns (uint128) { AppStorage storage s = LibAppStorage.diamondStorage(); return s.nextFid[id]; } + /** + * @dev Sets the next fertilizer id in the list given a fertilizer id. + * nextFid is a linked list of Fertilizer Ids ordered by Id number. (See AppStorage) + * @param id The id of the fertilizer. + * @param next The id of the next fertilizer. + */ function setNext(uint128 id, uint128 next) internal { AppStorage storage s = LibAppStorage.diamondStorage(); s.nextFid[id] = next; diff --git a/protocol/contracts/libraries/LibStrings.sol b/protocol/contracts/libraries/LibStrings.sol index 9bdd62d1eb..6f6ed84776 100644 --- a/protocol/contracts/libraries/LibStrings.sol +++ b/protocol/contracts/libraries/LibStrings.sol @@ -62,4 +62,50 @@ library LibStrings { return string(abi.encodePacked("-", toString(uint256(-value)))); } } + + /** + * @notice Returns a substring of a string starting from startIndex and ending at endIndex. + * @param str - The string to extract from. + * @param startIndex - The index to start at. + * @param endIndex - The index to end at. + * Inspired by: https://ethereum.stackexchange.com/questions/31457/substring-in-solidity + */ + function substring( + string memory str, + uint startIndex, + uint endIndex + ) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory result = new bytes(endIndex - startIndex); + for (uint i = startIndex; i < endIndex; i++) { + result[i - startIndex] = strBytes[i]; + } + return string(result); + } + + /** + * @notice Formats a uint128 number with 6 decimals to a string with 2 decimals. + * @param number - The number to format. + * @return string - The formatted string. + */ + function formatUintWith6DecimalsTo2(uint128 number) + internal + pure + returns (string memory) + { + // Cast to uint256 to be compatible with toString + string memory numString = toString(uint256(number)); + + // If the number has fewer than 6 decimals, add trailing zeros + while (bytes(numString).length < 7) { + numString = string(abi.encodePacked("0", numString)); + } + + // Extract the integer part and the first 2 decimal places + string memory integerPart = substring(numString, 0, bytes(numString).length - 6); + string memory decimalPart = substring(numString, bytes(numString).length - 6, bytes(numString).length - 4); + + // Concatenate the integer part and the decimal part with a dot in between + return string(abi.encodePacked(integerPart, ".", decimalPart)); + } } diff --git a/protocol/contracts/mocks/mockFacets/MockFertilizerFacet.sol b/protocol/contracts/mocks/mockFacets/MockFertilizerFacet.sol index 9040c7385c..fa35ee4bec 100644 --- a/protocol/contracts/mocks/mockFacets/MockFertilizerFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockFertilizerFacet.sol @@ -37,4 +37,8 @@ contract MockFertilizerFacet is FertilizerFacet { function setBarnRaiseWell(address well) external { s.u[C.UNRIPE_LP].underlyingToken = well; } + + function setBpf(uint128 bpf) external { + s.bpf = bpf; + } } \ No newline at end of file diff --git a/protocol/contracts/tokens/Fertilizer/Fertilizer.sol b/protocol/contracts/tokens/Fertilizer/Fertilizer.sol index 842ee44536..6f844b9139 100644 --- a/protocol/contracts/tokens/Fertilizer/Fertilizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Fertilizer.sol @@ -17,6 +17,8 @@ interface IBS { function remainingRecapitalization() external view returns (uint256); } +// Inherits Internalizer thus inherits ERC1155Upgradeable and the uri function +// The end Fert Facet only gets the interface of this contract contract Fertilizer is Internalizer { event ClaimFertilizer(uint256[] ids, uint256 beans); @@ -25,6 +27,13 @@ contract Fertilizer is Internalizer { using SafeMathUpgradeable for uint256; using LibSafeMath128 for uint128; + /** + * @notice Calculates and updates the amount of beans a user should receive + * given a set of fertilizer ids. Callable only by the Beanstalk contract. + * @param account - the user to update + * @param ids - an array of fertilizer ids + * @param bpf - the current beans per fertilizer + */ function beanstalkUpdate( address account, uint256[] memory ids, @@ -33,6 +42,14 @@ contract Fertilizer is Internalizer { return __update(account, ids, uint256(bpf)); } + /** + * @notice Mints a fertilizer to an account using a users specified balance + * Called from FertilizerFacet.mintFertilizer() + * @param account - the account to mint to + * @param id - the id of the fertilizer to mint + * @param amount - the amount of fertilizer to mint + * @param bpf - the current beans per fertilizer + */ function beanstalkMint(address account, uint256 id, uint128 amount, uint128 bpf) external onlyOwner { if (_balances[id][account].amount > 0) { uint256[] memory ids = new uint256[](1); @@ -48,6 +65,12 @@ contract Fertilizer is Internalizer { ); } + /** + * @notice hadles state updates before a fertilizer transfer + * @param from - the account to transfer from + * @param to - the account to transfer to + * @param ids - an array of fertilizer ids + */ function _beforeTokenTransfer( address, // operator, address from, @@ -61,6 +84,13 @@ contract Fertilizer is Internalizer { _update(to, ids, bpf); } + /** + * @notice Calculates and transfers the rewarded beans + * from a set of fertilizer ids to an account's internal balance + * @param account - the user to update + * @param ids - an array of fertilizer ids + * @param bpf - the beans per fertilizer + */ function _update( address account, uint256[] memory ids, @@ -70,6 +100,14 @@ contract Fertilizer is Internalizer { if (amount > 0) IBS(owner()).payFertilizer(account, amount); } + /** + * @notice Calculates and updates the amount of beans a user should receive + * given a set of fertilizer ids and the current outstanding total beans per fertilizer + * @param account - the user to update + * @param ids - the fertilizer ids + * @param bpf - the current beans per fertilizer + * @return beans - the amount of beans to reward the fertilizer owner + */ function __update( address account, uint256[] memory ids, @@ -86,6 +124,13 @@ contract Fertilizer is Internalizer { emit ClaimFertilizer(ids, beans); } + /** + * @notice Returns the balance of fertilized beans of a fertilizer owner given + a set of fertilizer ids + * @param account - the fertilizer owner + * @param ids - the fertilizer ids + * @return beans - the amount of fertilized beans the fertilizer owner has + */ function balanceOfFertilized(address account, uint256[] memory ids) external view returns (uint256 beans) { uint256 bpf = uint256(IBS(owner()).beansPerFertilizer()); for (uint256 i; i < ids.length; ++i) { @@ -95,6 +140,13 @@ contract Fertilizer is Internalizer { } } + /** + * @notice Returns the balance of unfertilized beans of a fertilizer owner given + a set of fertilizer ids + * @param account - the fertilizer owner + * @param ids - the fertilizer ids + * @return beans - the amount of unfertilized beans the fertilizer owner has + */ function balanceOfUnfertilized(address account, uint256[] memory ids) external view returns (uint256 beans) { uint256 bpf = uint256(IBS(owner()).beansPerFertilizer()); for (uint256 i; i < ids.length; ++i) { @@ -102,10 +154,16 @@ contract Fertilizer is Internalizer { } } + /** + @notice Returns the value remaining to recapitalize beanstalk + */ function remaining() public view returns (uint256) { return IBS(owner()).remainingRecapitalization(); } + /** + @notice Returns the id a fertilizer will receive when minted + */ function getMintId() public view returns (uint256) { return uint256(IBS(owner()).getEndBpf()); } diff --git a/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol b/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol new file mode 100644 index 0000000000..48542543d1 --- /dev/null +++ b/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.6; +pragma experimental ABIEncoderV2; + +import "contracts/libraries/LibStrings.sol"; +import {LibStrings} from "contracts/libraries/LibStrings.sol"; +import {LibBytes64} from "contracts/libraries/LibBytes64.sol"; + +/** + * @title FertilizerImage + * @author deadmanwalking + */ + +// interface to interact with the Beanstalk contract +interface IBeanstalk { + function beansPerFertilizer() external view returns (uint128); + function getEndBpf() external view returns (uint128); + function getFertilizer(uint128) external view returns (uint256); +} +contract FertilizerImage { + + address internal constant BEANSTALK = 0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5; + + ////////////////////// CONSTANTS TO ASSEMBLE SVG //////////////////////////// + + string internal constant BASE_JSON_URI = "data:application/json;base64,"; + + // Start for all fert svgs + string private constant BASE_SVG_START = ''; + + // End for all fert svgs + string private constant BASE_SVG_END = ''; + + // Top for the available fert svg + string private constant FERT_TOP_AVAILABLE = ' '; + + // Top for the active fert svg + string private constant FERT_TOP_ACTIVE =''; + + /** + * @dev imageURI returns the base64 encoded image URI representation of the Fertilizer + * @param _id - the id of the Fertilizer + * @param bpfRemaining - the bpfRemaining of the Fertilizer + * @return imageUri - the image URI representation of the Fertilizer + */ + function imageURI(uint256 _id, uint128 bpfRemaining) public view returns (string memory) { + return svgToImageURI(generateImageSvg(_id, bpfRemaining)); + } + + /////////////// FERTILIZER SVG ORDER /////////////////// + // SVG_HEADER + // BASE_SVG_START + // FERT_SVG_TOP (available, active) + // BASE_SVG_END + // SVG_PRE_NUMBER + // BPF_REMAINING + // END OF SVG + + /** + * @dev generateImageSvg assembles the needed components for the Fertilizer svg + * For use in the on-chain json fertilizer metadata + * @param _id - the id of the Fertilizer + * @param bpfRemaining - the bpfRemaining of the Fertilizer + * @return imageUri - the image URI representation of the Fertilizer + */ + function generateImageSvg(uint256 _id, uint128 bpfRemaining) internal view returns (string memory) { + return string( + abi.encodePacked( + '', // SVG HEADER + BASE_SVG_START, // BASE SVG START + getFertilizerStatusSvg(_id, bpfRemaining), // FERT_SVG_TOP (available, active) + BASE_SVG_END, // BASE SVG END + '', // PRE NUMBER FOR BPF REMAINING + LibStrings.formatUintWith6DecimalsTo2(bpfRemaining), // BPF_REMAINING with 2 decimal places + " BPF Remaining " // END OF SVG + ) + ); + } + + /** + * @dev Returns the correct svg top for the Fertilizer status based on the bpfRemaining. + * @param _id - the id of the Fertilizer + * @param bpfRemaining - the bpfRemaining of the Fertilizer + * @return fertilizerStatusSvg an svg top for the correct Fertilizer status + */ + function getFertilizerStatusSvg(uint256 _id, uint128 bpfRemaining) internal view returns (string memory) { + + uint256 fertilizerSupply = IBeanstalk(BEANSTALK).getFertilizer( + uint128(_id) + ); + + string memory fertilizerStatusSvg = FERT_TOP_AVAILABLE; + + if (fertilizerSupply > 0) { + fertilizerStatusSvg = bpfRemaining > 0 + ? FERT_TOP_ACTIVE + : ''; // a used fert (bpfRemaining = 0) has no top + } + + return fertilizerStatusSvg; + } + + /// @dev Helper function that converts an svg to a bade64 encoded image URI. + function svgToImageURI(string memory svg) + internal + pure + returns (string memory) + { + return string( + abi.encodePacked("data:image/svg+xml;base64,", LibBytes64.encode(bytes(string(abi.encodePacked(svg))))) + ); + } + +} \ No newline at end of file diff --git a/protocol/contracts/tokens/Fertilizer/Internalizer.sol b/protocol/contracts/tokens/Fertilizer/Internalizer.sol index 1fa31e1ceb..73626343a4 100644 --- a/protocol/contracts/tokens/Fertilizer/Internalizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Internalizer.sol @@ -12,16 +12,19 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./Fertilizer1155.sol"; import "contracts/libraries/LibSafeMath32.sol"; import "contracts/libraries/LibSafeMath128.sol"; +import "./FertilizerImage.sol"; +import {LibBytes64} from "contracts/libraries/LibBytes64.sol"; /** - * @author publius + * @author publius, deadmanwalking * @title Fertilizer before the Unpause */ -contract Internalizer is OwnableUpgradeable, ReentrancyGuardUpgradeable, Fertilizer1155 { +contract Internalizer is OwnableUpgradeable, ReentrancyGuardUpgradeable, Fertilizer1155, FertilizerImage { using SafeERC20Upgradeable for IERC20; using LibSafeMath128 for uint128; + using LibStrings for uint256; struct Balance { uint128 amount; @@ -38,13 +41,70 @@ contract Internalizer is OwnableUpgradeable, ReentrancyGuardUpgradeable, Fertili string private _uri; - function uri(uint256 _id) external view virtual override returns (string memory) { - return string(abi.encodePacked(_uri, StringsUpgradeable.toString(_id))); + ///////////////////// NEW URI FUNCTION /////////////////////// + + /** + * @notice Assembles and returns a base64 encoded json metadata + * URI for a given fertilizer ID. + * Need to override because the contract indirectly + * inherits from ERC1155. + * @param _id - the id of the fertilizer + * @return - the json metadata URI + */ + function uri(uint256 _id) + external + view + virtual + override + returns (string memory) + { + + uint128 bpfRemaining = calculateBpfRemaining(_id); + + // generate the image URI + string memory imageUri = imageURI(_id , bpfRemaining); + + // assemble and return the json URI + return ( + string( + abi.encodePacked( + BASE_JSON_URI, + LibBytes64.encode( + bytes( + abi.encodePacked( + '{"name": "Fertilizer - ', + _id.toString(), + '", "external_url": "https://fert.bean.money/', + _id.toString(), + '.html", ', + '"description": "A trusty constituent of any Farmers toolbox, ERC-1155 FERT has been known to spur new growth on seemingly dead farms. Once purchased and deployed into fertile ground by Farmers, Fertilizer generates new Sprouts: future Beans yet to be repaid by Beanstalk in exchange for doing the work of Replanting the protocol.", "image": "', + imageUri, + '", "attributes": [{ "trait_type": "BPF Remaining","display_type": "boost_number","value": ', + LibStrings.formatUintWith6DecimalsTo2(bpfRemaining), + " }]}" + ) + ) + ) + ) + ) + ); } - function setURI(string calldata newuri) public onlyOwner { - _uri = newuri; - } + /** + * @notice Returns the beans per fertilizer remaining for a given fertilizer Id. + * @param id - the id of the fertilizer + * Formula: bpfRemaining = s.bpf - id + * Calculated here to avoid uint underflow + * Solidity 0.8.0 has underflow protection and the tx would revert but we are using 0.7.6 + */ + function calculateBpfRemaining(uint256 id) internal view returns (uint128) { + // make sure it does not underflow + if (IBeanstalk(BEANSTALK).beansPerFertilizer() >= uint128(id)) { + return IBeanstalk(BEANSTALK).beansPerFertilizer() - uint128(id); + } else { + return 0; + } + } function name() public pure returns (string memory) { return "Fertilizer"; diff --git a/protocol/test/Fertilizer.test.js b/protocol/test/Fertilizer.test.js index 14b11005e5..47df0020b1 100644 --- a/protocol/test/Fertilizer.test.js +++ b/protocol/test/Fertilizer.test.js @@ -7,6 +7,7 @@ const { BEAN, USDC, UNRIPE_BEAN, UNRIPE_LP, BEANSTALK, BARN_RAISE_TOKEN } = requ const { setWstethUsdPrice } = require('../utils/oracle.js'); const { to6, to18 } = require('./utils/helpers.js'); const { deployBasinV1_1 } = require('../scripts/basinV1_1.js'); +const axios = require('axios') let user,user2,owner,fert let userAddress, ownerAddress, user2Address @@ -816,5 +817,129 @@ describe('Fertilize', function () { expect(b[3]).to.be.equal('150') }) }) + + describe("1 mint with uri", async function () { + + let mintReceipt; + + beforeEach(async function () { + + // Humidity 25000 + await this.season.teleportSunrise("6074"); + + // getFertilizers returns array with [fertid, supply] values + // before mint 2500000,100 so only 1 fert has been minted before this test + + // Maths: + // uint128 current season bpf = Humidity + 1000 * 1,000 // so 2500 + 1000 * 1,000 = 3500000 correct + // uint128 endBpf = totalbpf (s.bpf) + current season bpf; // so 0 + 3500000 = 3500000 correct + // uint128 bpfRemaining = totalbpf (s.bpf) - id; // so 0 - 3500000 = -3500000 correct but since it is uint128 it is 340282366920938463463374607431764711456 --> loops back + // uint128 fertilizer id = current season bpf + totalbpf // so 3500000 + 0 = 3500000 correct + // uint128 s.bpf // 0 + // Humidity // 2500 + + // Svg choice: + // If Fertilizer is not sold yet (fertilizer[id] == getFertilizer(id) == default == 0), it’s Available. + // If Fertilizer still has Sprouts (is owed Bean mints), it’s Active. bpfRemaining > 0 + // If Fertilizer has no more Sprouts (is done earning Bean mints), it’s Used. bpfRemaining = 0 + + // mint fert with id 3500000 and supply 50 + mintTx = await this.fertilizer.connect(user).mintFertilizer(to18('0.05'), '0', '0') + + mintReceipt = await mintTx.wait(); + + }); + + // Available fert test + it("returns an available fertilizer svg and stats when supply (fertilizer[id]) is 0", async function () { + + // Manipulate bpf to 5000000 + // new bpfremaining for id 350001 = 5000000 - 3500001 = 1499999 + await this.fertilizer.setBpf(5000000); + + // This returns an available image of fert + const availableDataImage = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjk0IiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDI5NCA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0xNjQuNDcgMzI3LjI0MSAyOC42MjUgNDA1Ljc2OGwtLjg3OC0yMjEuNTUxIDEzNS44NDktNzguNTU5Ljg3NCAyMjEuNTgzWiIgZmlsbD0iIzNEQUE0NyIvPjxwYXRoIGQ9Im0xMTguMDU5IDM1NC4wNzctNDEuMTAyIDIzLjc0Ni0uODc0LTIyMS41NTEgNDEuMTAxLTIzLjc3OC44NzUgMjIxLjU4M1oiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjYuODI1IDE4NC4yNDIuODcgMjIxLjU2NyA5My4zNjcgNTQuMzM5LS44NzEtMjIxLjU2NC05My4zNjYtNTQuMzQyWm0xMzYuNDMyLTc4LjI2Mi44NzEgMjIxLjU2OCA5My4zNjcgNTQuMzM4LS44NzEtMjIxLjU2NC05My4zNjctNTQuMzQyWiIgZmlsbD0iIzNEQjU0MiIvPjxwYXRoIGQ9Ik03Ni42MzQgMTYyLjkxNSAyMTIgODQuMTMzbDQ0LjAzNCA3NS45MDktMTM1Ljg0MiA3OC41NDQtNDMuNTU3LTc1LjY3MVoiIGZpbGw9IiM4MUQ2NzIiLz48cGF0aCBkPSJtMTI0Ljk2NiAxMzQuOTcgNDAuNjI0LTI0LjAwMSA0NC4wMzEgNzUuOTA2LTQxLjA5OCAyMy43NjUtNDMuNTU3LTc1LjY3WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMTIuMTI1IDQ3LjkxOC0uMTE2IDM2LjIyOC0xMzUuMzk0IDc4Ljc2Ni4xMTYtMzYuMTdjMC0yLjAzMi0xLjM5LTQuNDEzLTMuMTMtNS40NTctLjg3LS41MjMtMS42OC0uNTIzLTIuMjYxLS4yMzNsMTM1LjM5NC03OC43NjZjLjU4LS4zNDkgMS4zMzItLjI5IDIuMjAzLjIzMyAxLjczNi45ODkgMy4xODggMy40MjUgMy4xODggNS40WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0xNjUuNzEzIDc0Ljc1Mi0uMTE2IDM2LjIyOC00MC42NSAyMy45ODguMTE2LTM2LjE3YzAtMi4wMzItMS4zOS00LjQxMy0zLjEyOS01LjQ1Ny0uODcyLS41MjMtMS42ODEtLjUyMy0yLjI2Mi0uMjMybDQwLjY1LTIzLjk4OWMuNTgtLjM0OSAxLjMzMi0uMjkgMi4yMDMuMjMzIDEuNzM5Ljk4NiAzLjE4OCAzLjQyNSAzLjE4OCA1LjRaIiBmaWxsPSIjNDJBODRDIi8+PHBhdGggZD0iTTczLjU3OSAxMjEuMjk4YzEuNzM5IDEuMDA1IDMuMTYyIDMuNDIyIDMuMTU5IDUuNDI1bC0uMTA0IDM2LjE5MyA0My41NTcgNzUuNjY3LTkzLjM2Ni01NC4zMzkgNDMuNTIxLTI1LjAxOC4xMDMtMzYuMTQxYy4wMDQtMiAxLjM5LTIuNzk1IDMuMTMtMS43ODdaIiBmaWxsPSIjMkM5QTJDIi8+PHBhdGggZD0iTTEwNy44NzkgMjI2Ljc2NiAzNi42MiAxODUuNTY1bDM1Ljc0Mi0yMC4zOTUgMTEuNDI4IDE5Ljc5NCAyNC4wODkgNDEuODAyWiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im04MS4zNDggMTgwLjczMS00NC43MjggNC44MzQgMzUuNzQyLTIwLjM5NSA4Ljk4NiAxNS41NjFaIiBmaWxsPSIjODFENjcyIi8+ICA8cGF0aCBkPSJNOTUuNDkzIDIwOS4yMzdjLTkuNDQ3IDIuOTY2LTE3Ljg0NSAxMC42MzctMjEuNjIgMjEuNTUyLS40OTcgMS41ODktMi42NzggMS41ODktMy4yNzIgMC0zLjI3Mi0xMC4yMy0xMS40MDUtMTguMjc2LTIxLjUyLTIxLjU1Mi0xLjc4NC0uNTk4LTEuNzg0LTIuNzgyIDAtMy4zNzcgMTAuMTE1LTMuMzEyIDE4LjE3NC0xMS41MDYgMjEuNTItMjEuNTUyLjU5NC0xLjY4OSAyLjc3OC0xLjY4OSAzLjI3MiAwIDMuNzY4IDEwLjY4OSAxMS41NjMgMTguMTk1IDIxLjYyIDIxLjU1MiAxLjY4Ny41OTUgMS42ODcgMi43NzkgMCAzLjM3N1oiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjU2Ljg5OCAzODEuNjA5LTEzNS44NDYgNzguNTI3LS44NzctMjIxLjU1MSAxMzUuODQ5LTc4LjU2Ljg3NCAyMjEuNTg0WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0yMTAuNDg2IDQwOC40NDUtNDEuMTAxIDIzLjc0NS0uODc1LTIyMS41NTEgNDEuMTAyLTIzLjc3OC44NzQgMjIxLjU4NFoiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjQwLjkwMSAzNjQuOTQ5LTEwNC40MDcgNjAuMzg3LS4zMjMtMTU3LjQ3NyAxMDQuNDA4LTYwLjM1MS4zMjIgMTU3LjQ0MVoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTk1Ljc4OSAyNjguMDI1YzIzLjEzNy02LjcxNCAzNi44NzUgMTAuNjMxIDMyLjMwNiAzNS4yMzMtNC4wMiAyMS42NTItMjEuMzUyIDQyLjg0NS0zOS43NjkgNDkuODIxLTE5LjE3MSA3LjI2LTM1LjcxNy0yLjI2OC0zNi4yOTctMjMuOTY2LS42NjUtMjQuOTIyIDE5LjQxMy01NC4wMjEgNDMuNzYtNjEuMDg4WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMDYuNDE3IDI3NS42MTUtMjguMDggNzMuNTc3cy0yNC41NjktMzUuMzk3IDI4LjA4LTczLjU3N1ptLTIzLjAyNyA2OC4zNjIgMTkuNTYxLTUwLjkxNnMyMy44MzEgMTcuMTg5LTE5LjU2MSA1MC45MTZaIiBmaWxsPSIjZmZmIi8+PHRleHQgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjAiIHg9IjIwIiB5PSI0OTAiIGZpbGw9ImJsYWNrIiA+PHRzcGFuIGR5PSIwIiB4PSIyMCI+MS40OSBCUEYgUmVtYWluaW5nIDwvdHNwYW4+PC90ZXh0Pjwvc3ZnPg==" + + const availabletokenId = 3500001; // non minted fert id + const uri = await this.fert.uri(availabletokenId); + + const response = await axios.get(uri); + jsonResponse = JSON.parse(response.data.toString()); + + // id and image check + expect(jsonResponse.name).to.be.equal(`Fertilizer - ${availabletokenId}`); + expect(jsonResponse.image).to.be.equal(availableDataImage); + + // BPF Remaining json attribute check + expect(jsonResponse.attributes[0].trait_type).to.be.equal(`BPF Remaining`); + expect(jsonResponse.attributes[0].value.toString()).to.be.equal(`1.49`); + }); + + // Active fert test + it("returns an active fertilizer svg and stats when bpfRemaining > 0 and fert supply > 0", async function () { + + // Manipulate bpf to 5000000 + await this.fertilizer.setBpf(5000000); + + // uint128 endBpf = totalbpf (s.bpf) + current season bpf; + // So endbpf = 5000000 + 3500000 = 8500000 + // bpfRemaining = (s.bpf) - id; + // bpfRemaining for id 3500000 = 5000000 - 3500000 = 1500000 + // so bpfRemaining > 0 --> and fertsupply = 50 --> Active + // s.bpf = bpfremaining + id + + // This returns a active image of fert + const activeDataImage = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjk0IiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDI5NCA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0xNjQuNDcgMzI3LjI0MSAyOC42MjUgNDA1Ljc2OGwtLjg3OC0yMjEuNTUxIDEzNS44NDktNzguNTU5Ljg3NCAyMjEuNTgzWiIgZmlsbD0iIzNEQUE0NyIvPjxwYXRoIGQ9Im0xMTguMDU5IDM1NC4wNzctNDEuMTAyIDIzLjc0Ni0uODc0LTIyMS41NTEgNDEuMTAxLTIzLjc3OC44NzUgMjIxLjU4M1oiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjYuODI1IDE4NC4yNDIuODcgMjIxLjU2NyA5My4zNjcgNTQuMzM5LS44NzEtMjIxLjU2NC05My4zNjYtNTQuMzQyWm0xMzYuNDMyLTc4LjI2Mi44NzEgMjIxLjU2OCA5My4zNjcgNTQuMzM4LS44NzEtMjIxLjU2NC05My4zNjctNTQuMzQyWiIgZmlsbD0iIzNEQjU0MiIvPjxlbGxpcHNlIGN4PSIxMTMuMjQ3IiBjeT0iMjIwLjY4OCIgcng9IjM4LjcxNyIgcnk9IjM4Ljc3NCIgZmlsbD0iIzdGNTUzMyIvPjxlbGxpcHNlIGN4PSIxMTMuMjQ3IiBjeT0iMjIwLjY4OCIgcng9IjM4LjcxNyIgcnk9IjM4Ljc3NCIgZmlsbD0iIzdGNTUzMyIvPjxlbGxpcHNlIGN4PSI3MC4wMTMiIGN5PSIyMzYuODQ0IiByeD0iMzguNzE3IiByeT0iMzguNzc0IiBmaWxsPSIjN0Y1NTMzIi8+PHBhdGggZD0ibTI2LjgyNSAxODQuMjQyLjg3IDIyMS41NjcgOTMuMzY3IDU0LjMzOS0uODcxLTIyMS41NjQtOTMuMzY2LTU0LjM0MlptMTM2LjQzMi03OC4yNjIuODcxIDIyMS41NjggOTMuMzY3IDU0LjMzOC0uODcxLTIyMS41NjQtOTMuMzY3LTU0LjM0MloiIGZpbGw9IiMzREI1NDIiLz48ZWxsaXBzZSBjeD0iMTU2LjgwNSIgY3k9IjE5OC43MTUiIHJ4PSIzOC43MTciIHJ5PSIzOC43NzQiIGZpbGw9IiM3RjU1MzMiLz48ZWxsaXBzZSBjeD0iMTk4LjEwMyIgY3k9IjE4OS42NjgiIHJ4PSIzOC43MTciIHJ5PSIzOC43NzQiIGZpbGw9IiM3RjU1MzMiLz48cGF0aCBkPSJtMjU2Ljg5OCAzODEuNjA5LTEzNS44NDYgNzguNTI3LS44NzctMjIxLjU1MSAxMzUuODQ5LTc4LjU2Ljg3NCAyMjEuNTg0WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0yMTAuNDg2IDQwOC40NDUtNDEuMTAxIDIzLjc0NS0uODc1LTIyMS41NTEgNDEuMTAyLTIzLjc3OC44NzQgMjIxLjU4NFoiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjQwLjkwMSAzNjQuOTQ5LTEwNC40MDcgNjAuMzg3LS4zMjMtMTU3LjQ3NyAxMDQuNDA4LTYwLjM1MS4zMjIgMTU3LjQ0MVoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTk1Ljc4OSAyNjguMDI1YzIzLjEzNy02LjcxNCAzNi44NzUgMTAuNjMxIDMyLjMwNiAzNS4yMzMtNC4wMiAyMS42NTItMjEuMzUyIDQyLjg0NS0zOS43NjkgNDkuODIxLTE5LjE3MSA3LjI2LTM1LjcxNy0yLjI2OC0zNi4yOTctMjMuOTY2LS42NjUtMjQuOTIyIDE5LjQxMy01NC4wMjEgNDMuNzYtNjEuMDg4WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMDYuNDE3IDI3NS42MTUtMjguMDggNzMuNTc3cy0yNC41NjktMzUuMzk3IDI4LjA4LTczLjU3N1ptLTIzLjAyNyA2OC4zNjIgMTkuNTYxLTUwLjkxNnMyMy44MzEgMTcuMTg5LTE5LjU2MSA1MC45MTZaIiBmaWxsPSIjZmZmIi8+PHRleHQgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjAiIHg9IjIwIiB5PSI0OTAiIGZpbGw9ImJsYWNrIiA+PHRzcGFuIGR5PSIwIiB4PSIyMCI+MS41MCBCUEYgUmVtYWluaW5nIDwvdHNwYW4+PC90ZXh0Pjwvc3ZnPg==" + + // FertilizerFacet.mintFertilizer: id: 3500000 + const activeTokenId = 3500000 + + const uri = await this.fert.uri(activeTokenId); + + const response = await axios.get(uri); + jsonResponse = JSON.parse(response.data.toString()); + + // id and image check + expect(jsonResponse.name).to.be.equal(`Fertilizer - ${activeTokenId}`); + expect(jsonResponse.image).to.be.equal(activeDataImage); + + // BPF Remaining json attribute check + expect(jsonResponse.attributes[0].trait_type).to.be.equal(`BPF Remaining`); + expect(jsonResponse.attributes[0].value.toString()).to.be.equal(`1.5`); + }); + + // Used fert test + it("returns a used fertilizer svg and stats when bpfRemaining = 0", async function () { + + // bpf is 0 + // uint128 endBpf = totalbpf (s.bpf) + current season bpf; + // endbpf = 0 + 3500000 = 3500000 + // bpfRemaining = (s.bpf) - id; ---> 0 - 3500000 = -3500000 --> 340282366920938... because of underflow + // bpfremaining --> now returns 0 beacause of calcualte bpfRemaining function check + // so bpfRemaining = 0 --> Used + + // This returns a used image of fert + const usedDataImage = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjk0IiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDI5NCA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0xNjQuNDcgMzI3LjI0MSAyOC42MjUgNDA1Ljc2OGwtLjg3OC0yMjEuNTUxIDEzNS44NDktNzguNTU5Ljg3NCAyMjEuNTgzWiIgZmlsbD0iIzNEQUE0NyIvPjxwYXRoIGQ9Im0xMTguMDU5IDM1NC4wNzctNDEuMTAyIDIzLjc0Ni0uODc0LTIyMS41NTEgNDEuMTAxLTIzLjc3OC44NzUgMjIxLjU4M1oiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjYuODI1IDE4NC4yNDIuODcgMjIxLjU2NyA5My4zNjcgNTQuMzM5LS44NzEtMjIxLjU2NC05My4zNjYtNTQuMzQyWm0xMzYuNDMyLTc4LjI2Mi44NzEgMjIxLjU2OCA5My4zNjcgNTQuMzM4LS44NzEtMjIxLjU2NC05My4zNjctNTQuMzQyWiIgZmlsbD0iIzNEQjU0MiIvPjxwYXRoIGQ9Im0yNTYuODk4IDM4MS42MDktMTM1Ljg0NiA3OC41MjctLjg3Ny0yMjEuNTUxIDEzNS44NDktNzguNTYuODc0IDIyMS41ODRaIiBmaWxsPSIjNkRDQjYwIi8+PHBhdGggZD0ibTIxMC40ODYgNDA4LjQ0NS00MS4xMDEgMjMuNzQ1LS44NzUtMjIxLjU1MSA0MS4xMDItMjMuNzc4Ljg3NCAyMjEuNTg0WiIgZmlsbD0iIzNEQUE0NyIvPjxwYXRoIGQ9Im0yNDAuOTAxIDM2NC45NDktMTA0LjQwNyA2MC4zODctLjMyMy0xNTcuNDc3IDEwNC40MDgtNjAuMzUxLjMyMiAxNTcuNDQxWiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xOTUuNzg5IDI2OC4wMjVjMjMuMTM3LTYuNzE0IDM2Ljg3NSAxMC42MzEgMzIuMzA2IDM1LjIzMy00LjAyIDIxLjY1Mi0yMS4zNTIgNDIuODQ1LTM5Ljc2OSA0OS44MjEtMTkuMTcxIDcuMjYtMzUuNzE3LTIuMjY4LTM2LjI5Ny0yMy45NjYtLjY2NS0yNC45MjIgMTkuNDEzLTU0LjAyMSA0My43Ni02MS4wODhaIiBmaWxsPSIjNDZCOTU1Ii8+PHBhdGggZD0ibTIwNi40MTcgMjc1LjYxNS0yOC4wOCA3My41NzdzLTI0LjU2OS0zNS4zOTcgMjguMDgtNzMuNTc3Wm0tMjMuMDI3IDY4LjM2MiAxOS41NjEtNTAuOTE2czIzLjgzMSAxNy4xODktMTkuNTYxIDUwLjkxNloiIGZpbGw9IiNmZmYiLz48dGV4dCBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIyMCIgeD0iMjAiIHk9IjQ5MCIgZmlsbD0iYmxhY2siID48dHNwYW4gZHk9IjAiIHg9IjIwIj4wLjAwIEJQRiBSZW1haW5pbmcgPC90c3Bhbj48L3RleHQ+PC9zdmc+" + + // FertilizerFacet.mintFertilizer: id: 3500000 + const usedTokenId = 3500000 + + const uri = await this.fert.uri(usedTokenId); + + const response = await axios.get(uri); + + jsonResponse = JSON.parse(response.data.toString()); + + // id and image check + expect(jsonResponse.name).to.be.equal(`Fertilizer - ${usedTokenId}`); + expect(jsonResponse.image).to.be.equal(usedDataImage); + + // BPF Remaining json attribute check + expect(jsonResponse.attributes[0].trait_type).to.be.equal(`BPF Remaining`); + expect(jsonResponse.attributes[0].value.toString()).to.be.equal(`0`); + }); + + }); + }) }) \ No newline at end of file From 23149b5b5e2a4bd6d987e875db748b142cfb52e9 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 22 Mar 2024 16:16:18 +0200 Subject: [PATCH 03/86] Import bdv decrease changes --- .../contracts/beanstalk/silo/ConvertFacet.sol | 52 ++++--- .../libraries/Convert/LibConvert.sol | 19 ++- .../libraries/Convert/LibConvertData.sol | 16 ++- .../libraries/Convert/LibLambdaConvert.sol | 29 +++- .../mocks/mockFacets/MockConvertFacet.sol | 18 ++- .../mocks/mockFacets/MockSiloFacet.sol | 26 ++++ protocol/test/Convert.test.js | 133 ++++++++++++++++-- protocol/test/utils/encoder.js | 9 +- 8 files changed, 257 insertions(+), 45 deletions(-) diff --git a/protocol/contracts/beanstalk/silo/ConvertFacet.sol b/protocol/contracts/beanstalk/silo/ConvertFacet.sol index e9ae60907a..bb39b22e26 100644 --- a/protocol/contracts/beanstalk/silo/ConvertFacet.sol +++ b/protocol/contracts/beanstalk/silo/ConvertFacet.sol @@ -75,30 +75,39 @@ contract ConvertFacet is ReentrancyGuard { nonReentrant returns (int96 toStem, uint256 fromAmount, uint256 toAmount, uint256 fromBdv, uint256 toBdv) { - address toToken; address fromToken; uint256 grownStalk; + address toToken; address fromToken; uint256 grownStalk; address account; bool decreaseBDV; - (toToken, fromToken, toAmount, fromAmount) = LibConvert.convert(convertData); + (toToken, fromToken, toAmount, fromAmount, account, decreaseBDV) = LibConvert.convert(convertData); require(fromAmount > 0, "Convert: From amount is 0."); require(fromAmount > 0, "Convert: From amount is 0."); - LibSilo._mow(msg.sender, fromToken); - LibSilo._mow(msg.sender, toToken); + // Replace account with msg.sender if no account is specified. + if(account == address(0)) account = msg.sender; + + LibSilo._mow(account, fromToken); + + // If the fromToken and toToken are different, mow the toToken as well. + if (fromToken != toToken) LibSilo._mow(account, toToken); + + // Withdraw the tokens from the deposit. (grownStalk, fromBdv) = _withdrawTokens( fromToken, stems, amounts, - fromAmount + fromAmount, + account ); - // calculate the bdv of the new deposit + // Calculate the bdv of the new deposit. uint256 newBdv = LibTokenSilo.beanDenominatedValue(toToken, toAmount); - toBdv = newBdv > fromBdv ? newBdv : fromBdv; + // If `decreaseBDV` flag is not enabled, set toBDV to the max of the two bdvs. + toBdv = (newBdv > fromBdv || decreaseBDV) ? newBdv : fromBdv; - toStem = _depositTokensForConvert(toToken, toAmount, toBdv, grownStalk); - emit Convert(msg.sender, fromToken, toToken, fromAmount, toAmount); + toStem = _depositTokensForConvert(toToken, toAmount, toBdv, grownStalk, account); + emit Convert(account, fromToken, toToken, fromAmount, toAmount); } /** @@ -113,7 +122,8 @@ contract ConvertFacet is ReentrancyGuard { address token, int96[] memory stems, uint256[] memory amounts, - uint256 maxTokens + uint256 maxTokens, + address account ) internal returns (uint256, uint256) { require( stems.length == amounts.length, @@ -141,7 +151,7 @@ contract ConvertFacet is ReentrancyGuard { if (a.active.tokens.add(amounts[i]) >= maxTokens) amounts[i] = maxTokens.sub(a.active.tokens); depositBDV = LibTokenSilo.removeDepositFromAccount( - msg.sender, + account, token, stems[i], amounts[i] @@ -167,7 +177,7 @@ contract ConvertFacet is ReentrancyGuard { for (i; i < stems.length; ++i) amounts[i] = 0; emit RemoveDeposits( - msg.sender, + account, token, stems, amounts, @@ -176,8 +186,8 @@ contract ConvertFacet is ReentrancyGuard { ); emit LibSilo.TransferBatch( - msg.sender, - msg.sender, + account, + account, address(0), depositIds, amounts @@ -192,7 +202,7 @@ contract ConvertFacet is ReentrancyGuard { // all deposits converted are not germinating. LibSilo.burnActiveStalk( - msg.sender, + account, a.active.stalk.add(a.active.bdv.mul(s.ss[token].stalkIssuedPerBdv)) ); return (a.active.stalk, a.active.bdv); @@ -204,6 +214,7 @@ contract ConvertFacet is ReentrancyGuard { * @param amount the amount of tokens to deposit * @param bdv the bean denominated value of the deposit * @param grownStalk the amount of grown stalk retained to issue to the new deposit. + * @param account account to update the deposit (used in bdv decrease) * * @dev there are cases where a convert may cause the new deposit to be partially germinating, * if the convert goes from a token with a lower amount of seeds to a higher amount of seeds. @@ -213,7 +224,8 @@ contract ConvertFacet is ReentrancyGuard { address token, uint256 amount, uint256 bdv, - uint256 grownStalk + uint256 grownStalk, + address account ) internal returns (int96 stem) { require(bdv > 0 && amount > 0, "Convert: BDV or amount is 0."); @@ -229,17 +241,17 @@ contract ConvertFacet is ReentrancyGuard { if (germ == LibGerminate.Germinate.NOT_GERMINATING) { LibTokenSilo.incrementTotalDeposited(token, amount, bdv); LibSilo.mintActiveStalk( - msg.sender, + account, bdv.mul(LibTokenSilo.stalkIssuedPerBdv(token)).add(grownStalk) ); } else { LibTokenSilo.incrementTotalGerminating(token, amount, bdv, germ); // safeCast not needed as stalk is <= max(uint128) - LibSilo.mintGerminatingStalk(msg.sender, uint128(bdv.mul(LibTokenSilo.stalkIssuedPerBdv(token))), germ); - LibSilo.mintActiveStalk(msg.sender, grownStalk); + LibSilo.mintGerminatingStalk(account, uint128(bdv.mul(LibTokenSilo.stalkIssuedPerBdv(token))), germ); + LibSilo.mintActiveStalk(account, grownStalk); } LibTokenSilo.addDepositToAccount( - msg.sender, + account, token, stem, amount, diff --git a/protocol/contracts/libraries/Convert/LibConvert.sol b/protocol/contracts/libraries/Convert/LibConvert.sol index c9e1ab50d0..7795fe8a13 100644 --- a/protocol/contracts/libraries/Convert/LibConvert.sol +++ b/protocol/contracts/libraries/Convert/LibConvert.sol @@ -16,7 +16,7 @@ import {C} from "contracts/C.sol"; /** * @title LibConvert - * @author Publius + * @author Publius, deadmanwalking */ library LibConvert { using SafeMath for uint256; @@ -27,6 +27,10 @@ library LibConvert { * @notice Takes in bytes object that has convert input data encoded into it for a particular convert for * a specified pool and returns the in and out convert amounts and token addresses and bdv * @param convertData Contains convert input parameters for a specified convert + * note account and decreaseBDV variables are initialized at the start + * as address(0) and false respectively and remain that way if a convert is not anti-lambda-lambda + * If it is anti-lambda, account is the address of the account to update the deposit + * and decreaseBDV is true */ function convert(bytes calldata convertData) external @@ -34,7 +38,9 @@ library LibConvert { address tokenOut, address tokenIn, uint256 amountOut, - uint256 amountIn + uint256 amountIn, + address account, + bool decreaseBDV ) { LibConvertData.ConvertKind kind = convertData.convertKind(); @@ -63,6 +69,9 @@ library LibConvert { } else if (kind == LibConvertData.ConvertKind.UNRIPE_TO_RIPE) { (tokenOut, tokenIn, amountOut, amountIn) = LibChopConvert .convertUnripeToRipe(convertData); + } else if (kind == LibConvertData.ConvertKind.ANTI_LAMBDA_LAMBDA) { + (tokenOut, tokenIn, amountOut, amountIn, account, decreaseBDV) = LibLambdaConvert + .antiConvert(convertData); } else { revert("Convert: Invalid payload"); } @@ -82,7 +91,8 @@ library LibConvert { // if (tokenIn == C.BEAN && tokenOut == C.CURVE_BEAN_METAPOOL) // return LibCurveConvert.beansToPeg(C.CURVE_BEAN_METAPOOL); - // Lambda -> Lambda + // Lambda -> Lambda & + // Anti-Lambda -> Lambda if (tokenIn == tokenOut) return type(uint256).max; @@ -139,7 +149,8 @@ library LibConvert { if (tokenIn == C.UNRIPE_BEAN && tokenOut == C.UNRIPE_LP) return LibUnripeConvert.getLPAmountOut(amountIn); - // Lambda -> Lambda + // Lambda -> Lambda & + // Anti-Lambda -> Lambda if (tokenIn == tokenOut) return amountIn; diff --git a/protocol/contracts/libraries/Convert/LibConvertData.sol b/protocol/contracts/libraries/Convert/LibConvertData.sol index 35bd8b2e88..2cb019085a 100644 --- a/protocol/contracts/libraries/Convert/LibConvertData.sol +++ b/protocol/contracts/libraries/Convert/LibConvertData.sol @@ -17,7 +17,8 @@ library LibConvertData { LAMBDA_LAMBDA, BEANS_TO_WELL_LP, WELL_LP_TO_BEANS, - UNRIPE_TO_RIPE + UNRIPE_TO_RIPE, + ANTI_LAMBDA_LAMBDA } /// @notice Decoder for the Convert Enum @@ -65,4 +66,17 @@ library LibConvertData { { (, amount, token) = abi.decode(self, (ConvertKind, uint256, address)); } + + + /// @notice Decoder for the antiLambdaConvert + /// @dev contains an additional address parameter for the account to update the deposit + /// and a bool to indicate whether to decrease the bdv + function antiLambdaConvert(bytes memory self) + internal + pure + returns (uint256 amount, address token , address account, bool decreaseBDV) + { + (, amount, token , account) = abi.decode(self, (ConvertKind, uint256, address , address)); + decreaseBDV = true; + } } diff --git a/protocol/contracts/libraries/Convert/LibLambdaConvert.sol b/protocol/contracts/libraries/Convert/LibLambdaConvert.sol index f39c1bff5b..30f57e4072 100644 --- a/protocol/contracts/libraries/Convert/LibLambdaConvert.sol +++ b/protocol/contracts/libraries/Convert/LibLambdaConvert.sol @@ -7,11 +7,15 @@ import {LibConvertData} from "./LibConvertData.sol"; /** * @title LibLambdaConvert - * @author Publius + * @author Publius, deadmanwalking */ library LibLambdaConvert { using LibConvertData for bytes; + /** + * @notice This function returns the full input for use in lambda convert + * In lambda convert, the account converts from and to the same token. + */ function convert(bytes memory convertData) internal pure @@ -26,4 +30,27 @@ library LibLambdaConvert { tokenOut = tokenIn; amountOut = amountIn; } + + /** + * @notice This function returns the full input for use in anti-lamda convert + * In anti lamda convert, any user can convert on behalf of an account + * to update a deposit's bdv. + * This is why the additional 'account' parameter is returned. + */ + function antiConvert(bytes memory convertData) + internal + pure + returns ( + address tokenOut, + address tokenIn, + uint256 amountOut, + uint256 amountIn, + address account, + bool decreaseBDV + ) + { + (amountIn, tokenIn, account, decreaseBDV) = convertData.antiLambdaConvert(); + tokenOut = tokenIn; + amountOut = amountIn; + } } diff --git a/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol b/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol index 7a9b9658ea..1fb08b3bcd 100644 --- a/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol @@ -24,10 +24,12 @@ contract MockConvertFacet is ConvertFacet { address token, int96[] memory stems, uint256[] memory amounts, - uint256 maxTokens + uint256 maxTokens, + address account ) external { LibSilo._mow(msg.sender, token); - (uint256 stalkRemoved, uint256 bdvRemoved) = _withdrawTokens(token, stems, amounts, maxTokens); + if (account == address(0)) account = msg.sender; + (uint256 stalkRemoved, uint256 bdvRemoved) = _withdrawTokens(token, stems, amounts, maxTokens, account); emit MockConvert(stalkRemoved, bdvRemoved); @@ -37,10 +39,12 @@ contract MockConvertFacet is ConvertFacet { address token, uint256 amount, uint256 bdv, - uint256 grownStalk + uint256 grownStalk, + address account ) external { LibSilo._mow(msg.sender, token); - _depositTokensForConvert(token, amount, bdv, grownStalk); + if (account == address(0)) account = msg.sender; + _depositTokensForConvert(token, amount, bdv, grownStalk, account); } function convertInternalE( @@ -51,10 +55,12 @@ contract MockConvertFacet is ConvertFacet { address toToken, address fromToken, uint256 toAmount, - uint256 fromAmount + uint256 fromAmount, + address account, + bool decreaseBDV ) { IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - (toToken, fromToken, toAmount, fromAmount) = LibConvert.convert( + (toToken, fromToken, toAmount, fromAmount , account , decreaseBDV) = LibConvert.convert( convertData ); IERC20(toToken).safeTransfer(msg.sender, toAmount); diff --git a/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol b/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol index a4f3021448..6aae64691f 100644 --- a/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol @@ -55,6 +55,32 @@ contract MockSiloFacet is SiloFacet { return amount.mul(3).div(2); } + /// @dev Mocks a BDV decrease of 10 + function mockBDVDecrease(uint256 amount) external pure returns (uint256) { + return amount - 10; + } + + /// @dev Mocks a constant BDV of 1e6 + function newMockBDV() external pure returns (uint256) { + return 1e6; + } + + /// @dev Mocks a decrease in constant BDV + function newMockBDVDecrease() external pure returns (uint256) { + return 0.9e6; + } + + /// @dev Mocks an increase in constant BDV + function newMockBDVIncrease() external pure returns (uint256) { + return 1.1e6; + } + + /// @dev changes bdv selector of token + function mockChangeBDVSelector(address token, bytes4 selector) external { + AppStorage storage s = LibAppStorage.diamondStorage(); + s.ss[token].selector = selector; + } + function mockUnripeLPDeposit(uint256 t, uint32 _s, uint256 amount, uint256 bdv) external { _mowLegacy(msg.sender); if (t == 0) { diff --git a/protocol/test/Convert.test.js b/protocol/test/Convert.test.js index 9cad11f343..fc4972e4bb 100644 --- a/protocol/test/Convert.test.js +++ b/protocol/test/Convert.test.js @@ -52,10 +52,23 @@ describe('Convert', function () { // call sunrise twice, and end germination for the silo token, // so that both deposits are not germinating. + // user1 deposits 2 times at stem 1 and 2 100 silo tokens , so 100 bdv for each deposit await this.season.siloSunrise(0); await this.season.mockEndTotalGerminationForToken(this.siloToken.address); await this.season.siloSunrise(0); await this.season.mockEndTotalGerminationForToken(this.siloToken.address); + + // To isolate the anti lamda functionality, we will create and whitelist a new silo token + this.newSiloToken = await ethers.getContractFactory("MockToken"); + this.newSiloToken = await this.newSiloToken.deploy("Silo2", "SILO2") + await this.newSiloToken.deployed() + + await this.silo.mockWhitelistToken( + this.newSiloToken.address, // token + this.silo.interface.getSighash("newMockBDV()"), // selector (returns 1e6) + '1', // stalkIssuedPerBdv + 1e6 //aka "1 seed" // stalkEarnedPerSeason + ); }); beforeEach(async function () { @@ -69,24 +82,24 @@ describe('Convert', function () { describe('Withdraw For Convert', async function () { describe("Revert", async function () { it('diff lengths', async function () { - await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('1'), to6('2')], ['100'], '100')).to.be.revertedWith('Convert: stems, amounts are diff lengths.') + await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('1'), to6('2')], ['100'], '100', userAddress)).to.be.revertedWith('Convert: stems, amounts are diff lengths.') }); it('crate balance too low', async function () { //params are token, stem, amounts, maxtokens // await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, ['0'], ['150'], '150')).to.be.revertedWith('Silo: Crate balance too low.') //before moving to constants for the original 4 whitelisted tokens (post replant), this test would revert with 'Silo: Crate balance too low.', but now it reverts with 'Must line up with season' because there's no constant seeds amount hardcoded in for this test token - await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2')], ['150'], '150')).to.be.revertedWith('Silo: Crate balance too low.') + await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2')], ['150'], '150', userAddress)).to.be.revertedWith('Silo: Crate balance too low.') }); it('not enough removed', async function () { - await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2')], ['100'], '150')).to.be.revertedWith('Convert: Not enough tokens removed.') + await expect(this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2')], ['100'], '150', userAddress)).to.be.revertedWith('Convert: Not enough tokens removed.') }); }) //this test withdraws from stem index of 2, verifies they are removed correctly and stalk balances updated describe("Withdraw 1 Crate", async function () { beforeEach(async function () { - this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2')], ['100'], '100'); + this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2')], ['100'], '100', userAddress); }) it('Emits event', async function () { @@ -120,7 +133,7 @@ describe('Convert', function () { //this test withdraws from stem indexes of 2 and 1 describe("Withdraw 1 Crate 2 input", async function () { beforeEach(async function () { - this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2'), to6('1')], ['100', '100'], '100'); + this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('2'), to6('1')], ['100', '100'], '100', userAddress); }) it('Emits event', async function () { @@ -151,7 +164,7 @@ describe('Convert', function () { //withdraws less than the full deposited amount from stem indexes of 2 and 1 describe("Withdraw 2 Crates exact", async function () { beforeEach(async function () { - this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('1'), to6('2')], ['100', '50'], '150'); + this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('1'), to6('2')], ['100', '50'], '150', userAddress); }) it('Emits event', async function () { @@ -184,7 +197,7 @@ describe('Convert', function () { describe("Withdraw 2 Crates under", async function () { beforeEach(async function () { - this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('1'), to6('2')], ['100', '100'], '150'); + this.result = await this.convert.connect(user).withdrawForConvertE(this.siloToken.address, [to6('1'), to6('2')], ['100', '100'], '150', userAddress); }) it('Emits event', async function () { @@ -216,17 +229,17 @@ describe('Convert', function () { describe('Deposit For Convert', async function () { describe("Revert", async function () { it("Reverts if BDV is 0", async function () { - await expect(this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '0', '100')).to.be.revertedWith("Convert: BDV or amount is 0.") + await expect(this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '0', '100', user2Address)).to.be.revertedWith("Convert: BDV or amount is 0.") }) it("Reverts if amount is 0", async function () { - await expect(this.convert.connect(user2).depositForConvertE(this.siloToken.address, '0', '100', '100')).to.be.revertedWith("Convert: BDV or amount is 0.") + await expect(this.convert.connect(user2).depositForConvertE(this.siloToken.address, '0', '100', '100', user2Address)).to.be.revertedWith("Convert: BDV or amount is 0.") }) }) describe('Deposit Tokens No Grown', async function () { beforeEach(async function () { - this.result = await this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '100', '0'); + this.result = await this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '100', '0', user2Address); }); it('Emits event', async function () { @@ -266,7 +279,7 @@ describe('Convert', function () { expect(await this.siloGetters.getGerminatingTotalDeposited(this.siloToken.address)).to.equal('0'); expect(await this.siloGetters.getGerminatingTotalDepositedBdv(this.siloToken.address)).to.eq('0'); expect(await this.siloGetters.getTotalGerminatingStalk()).to.equal('0'); - this.result = await this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '100', '100'); + this.result = await this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '100', '100', user2Address); }); it('Emits event', async function () { @@ -308,7 +321,7 @@ describe('Convert', function () { expect(await this.siloGetters.getGerminatingTotalDeposited(this.siloToken.address)).to.equal('0'); expect(await this.siloGetters.getGerminatingTotalDepositedBdv(this.siloToken.address)).to.eq('0'); expect(await this.siloGetters.getTotalGerminatingStalk()).to.equal('0'); - this.result = await this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '100', '300'); + this.result = await this.convert.connect(user2).depositForConvertE(this.siloToken.address, '100', '100', '300', user2Address); }); it('Emits event', async function () { @@ -403,4 +416,100 @@ describe('Convert', function () { await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.siloToken.address, to6('1.5'), '200', '200'); }) }) + + // ------------------------------ ANTI LAMBDA CONVERT ---------------------------------- + + describe("anti lambda convert bdv decrease", async function () { + + beforeEach(async function () { + // ----------------------- SETUP ------------------------ + // user deposits 100 new silo token at stem 0 so 1000000 bdv + await this.newSiloToken.mint(userAddress, '10000000'); + await this.newSiloToken.connect(user).approve(this.silo.address, '1000000000'); + await this.silo.connect(user).deposit(this.newSiloToken.address, '100', EXTERNAL); + + // simulate deposit bdv decrease for user by changing bdv selector to newMockBDVDecrease ie 0.9e6 + await this.silo.mockChangeBDVSelector(this.newSiloToken.address, this.silo.interface.getSighash("newMockBDVDecrease()")) + const currentBdv = await this.silo.newMockBDVDecrease() + let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0) + const depositBdv = depositResult[1] + + // ----------------------- CONVERT ------------------------ + this.result = await this.convert.connect(user2).convert( + // CALLDATA // amount, token ,account + ConvertEncoder.convertAntiLambdaToLambda('100', this.newSiloToken.address , userAddress), + // STEMS [] + ['0'], + // AMOUNTS [] + ['100'] + ) + }) + + it('Correctly updates deposit stats', async function () { + let deposit = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0); + expect(deposit[0]).to.eq('100'); // deposit[0] = amount of tokens + expect(deposit[1]).to.eq('900000'); // deposit[1] = bdv + }) + + it('Correctly updates totals', async function () { + expect(await this.silo.getTotalDeposited(this.newSiloToken.address)).to.equal('100'); + expect(await this.silo.getTotalDepositedBdv(this.newSiloToken.address)).to.eq('900000'); + // 100000 stalk removed = 1 stalk/bdv for newSiloToken * 100000 bdv removed from convert + expect(await this.silo.totalStalk()).to.equal('2900100'); + }) + + it('Emits events', async function () { + await expect(this.result).to.emit(this.silo, 'RemoveDeposits').withArgs(userAddress, this.newSiloToken.address, [0], ['100'], '100', ['1000000']); + await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, 0, '100', '900000'); // last param = updated bdv + await expect(this.result).to.emit(this.convert, 'Convert').withArgs(userAddress, this.newSiloToken.address, this.newSiloToken.address, '100', '100'); + }) + + }) + + describe("anti lambda convert bdv increase", async function () { + + beforeEach(async function () { + // ----------------------- SETUP ------------------------ + // user deposits 100 new silo token at stem 0 so 1000000 bdv + await this.newSiloToken.mint(userAddress, '10000000'); + await this.newSiloToken.connect(user).approve(this.silo.address, '1000000000'); + await this.silo.connect(user).deposit(this.newSiloToken.address, '100', EXTERNAL); + + // simulate deposit bdv decrease for user2 by changing bdv selector to mockBdvIncrease ie 1.1e6 + await this.silo.mockChangeBDVSelector(this.newSiloToken.address, this.silo.interface.getSighash("newMockBDVIncrease()")) + currentBdv = await this.silo.newMockBDVIncrease() + let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0) + const depositBdv = depositResult[1] + + // ----------------------- CONVERT ------------------------ + this.result = await this.convert.connect(user2).convert( + // CALLDATA // amount, token ,account + ConvertEncoder.convertAntiLambdaToLambda('100', this.newSiloToken.address , userAddress), + // STEMS [] + ['0'], + // AMOUNTS [] + ['100'] + ) + }) + + it('Correctly updates deposit stats', async function () { + let deposit = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0); + expect(deposit[0]).to.eq('100'); // deposit[0] = amount of tokens + expect(deposit[1]).to.eq('1100000'); // deposit[1] = bdv + }) + + // it('Correctly updates totals', async function () { + // expect(await this.siloGetters.getTotalDeposited(this.newSiloToken.address)).to.equal('100'); + // expect(await this.siloGetters.getTotalDepositedBdv(this.newSiloToken.address)).to.eq('1100000'); + // // 100000 stalk added = 1 stalk/bdv for newSiloToken * 100000 bdv added from convert + // expect(await this.siloGetters.totalStalk()).to.equal('3100100'); + // }) + + // it('Emits events', async function () { + // await expect(this.result).to.emit(this.silo, 'RemoveDeposits').withArgs(userAddress, this.newSiloToken.address, [0], ['100'], '100', ['1000000']); + // await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, 0, '100', '1100000'); // last param = updated bdv + // await expect(this.result).to.emit(this.convert, 'Convert').withArgs(userAddress, this.newSiloToken.address, this.newSiloToken.address, '100', '100'); + // }) + + }) }); diff --git a/protocol/test/utils/encoder.js b/protocol/test/utils/encoder.js index d319fb22c8..915ace0008 100644 --- a/protocol/test/utils/encoder.js +++ b/protocol/test/utils/encoder.js @@ -8,7 +8,8 @@ const ConvertKind = { LAMBDA_LAMBDA: 4, BEANS_TO_WELL_LP: 5, WELL_LP_TO_BEANS: 6, - UNRIPE_TO_RIPE: 7 + UNRIPE_TO_RIPE: 7, + ANTI_LAMBDA_LAMBDA: 8, } class ConvertEncoder { @@ -83,6 +84,12 @@ class ConvertEncoder { ['uint256', 'uint256', 'address'], [ConvertKind.UNRIPE_TO_RIPE, unripeAmount, unripeToken] ); + + static convertAntiLambdaToLambda = (amount, token, account) => + defaultAbiCoder.encode( + ['uint256', 'uint256', 'address' , 'address'], + [ConvertKind.ANTI_LAMBDA_LAMBDA, amount, token , account] + ); } exports.ConvertEncoder = ConvertEncoder \ No newline at end of file From da86e57b123386f337ffe27c002b4e0d34c4e184 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 22 Mar 2024 15:26:48 -0400 Subject: [PATCH 04/86] fix --- .../mocks/mockFacets/MockSiloFacet.sol | 1 + protocol/scripts/deployFertilizer.js | 1 + protocol/test/Convert.test.js | 71 ++++++++++++------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol b/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol index 6aae64691f..b2854c6d94 100644 --- a/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSiloFacet.sol @@ -45,6 +45,7 @@ contract MockSiloFacet is SiloFacet { function mockWhitelistToken(address token, bytes4 selector, uint16 stalk, uint24 stalkEarnedPerSeason) external { whitelistTokenLegacy(token, selector, stalk, stalkEarnedPerSeason); + LibWhitelistedTokens.addWhitelistStatus(token, true, true, true); } function mockBDV(uint256 amount) external pure returns (uint256) { diff --git a/protocol/scripts/deployFertilizer.js b/protocol/scripts/deployFertilizer.js index 36763507e9..66fd62fa7e 100644 --- a/protocol/scripts/deployFertilizer.js +++ b/protocol/scripts/deployFertilizer.js @@ -32,6 +32,7 @@ async function deploy(account, pre=true, mock=false) { value: ethers.utils.parseEther('1') }) await hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [BCM] }); + await hre.network.provider.send("hardhat_setBalance", [BCM, "0x21E19E0C9BAB2400000"]); const bcm = await ethers.getSigner(BCM) await usdc.connect(bcm).transfer(USDC_MINTER, await usdc.balanceOf(BCM)); diff --git a/protocol/test/Convert.test.js b/protocol/test/Convert.test.js index fc4972e4bb..4e5089def8 100644 --- a/protocol/test/Convert.test.js +++ b/protocol/test/Convert.test.js @@ -427,11 +427,15 @@ describe('Convert', function () { await this.newSiloToken.mint(userAddress, '10000000'); await this.newSiloToken.connect(user).approve(this.silo.address, '1000000000'); await this.silo.connect(user).deposit(this.newSiloToken.address, '100', EXTERNAL); + this.stem = await this.siloGetters.stemTipForToken(this.newSiloToken.address); + // end germination: + await this.season.siloSunrise(0); + await this.season.siloSunrise(0); // simulate deposit bdv decrease for user by changing bdv selector to newMockBDVDecrease ie 0.9e6 await this.silo.mockChangeBDVSelector(this.newSiloToken.address, this.silo.interface.getSighash("newMockBDVDecrease()")) const currentBdv = await this.silo.newMockBDVDecrease() - let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0) + let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, this.stem) const depositBdv = depositResult[1] // ----------------------- CONVERT ------------------------ @@ -439,28 +443,34 @@ describe('Convert', function () { // CALLDATA // amount, token ,account ConvertEncoder.convertAntiLambdaToLambda('100', this.newSiloToken.address , userAddress), // STEMS [] - ['0'], + [this.stem], // AMOUNTS [] ['100'] ) + // inital bdv: 1000000 + // new bdv: 900000 + // grown stalk: 2e6 (newSiloToken has 1 seed). + // gspbdv = grown stalk / new bdv = 2.222222 + // stem = stemTip - gspbdv: 2 - 2.222222 = -0.222222 + this.newStem = -222222 }) it('Correctly updates deposit stats', async function () { - let deposit = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0); + let deposit = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, this.newStem); expect(deposit[0]).to.eq('100'); // deposit[0] = amount of tokens expect(deposit[1]).to.eq('900000'); // deposit[1] = bdv }) it('Correctly updates totals', async function () { - expect(await this.silo.getTotalDeposited(this.newSiloToken.address)).to.equal('100'); - expect(await this.silo.getTotalDepositedBdv(this.newSiloToken.address)).to.eq('900000'); + expect(await this.siloGetters.getTotalDeposited(this.newSiloToken.address)).to.equal('100'); + expect(await this.siloGetters.getTotalDepositedBdv(this.newSiloToken.address)).to.eq('900000'); // 100000 stalk removed = 1 stalk/bdv for newSiloToken * 100000 bdv removed from convert - expect(await this.silo.totalStalk()).to.equal('2900100'); + expect(await this.siloGetters.totalStalk()).to.equal('4900100'); }) it('Emits events', async function () { - await expect(this.result).to.emit(this.silo, 'RemoveDeposits').withArgs(userAddress, this.newSiloToken.address, [0], ['100'], '100', ['1000000']); - await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, 0, '100', '900000'); // last param = updated bdv + await expect(this.result).to.emit(this.silo, 'RemoveDeposits').withArgs(userAddress, this.newSiloToken.address, [this.stem], ['100'], '100', ['1000000']); + await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, this.newStem, '100', '900000'); // last param = updated bdv await expect(this.result).to.emit(this.convert, 'Convert').withArgs(userAddress, this.newSiloToken.address, this.newSiloToken.address, '100', '100'); }) @@ -474,42 +484,55 @@ describe('Convert', function () { await this.newSiloToken.mint(userAddress, '10000000'); await this.newSiloToken.connect(user).approve(this.silo.address, '1000000000'); await this.silo.connect(user).deposit(this.newSiloToken.address, '100', EXTERNAL); + this.stem = await this.siloGetters.stemTipForToken(this.newSiloToken.address); + + // end germination: + await this.season.siloSunrise(0); + await this.season.siloSunrise(0); // simulate deposit bdv decrease for user2 by changing bdv selector to mockBdvIncrease ie 1.1e6 await this.silo.mockChangeBDVSelector(this.newSiloToken.address, this.silo.interface.getSighash("newMockBDVIncrease()")) currentBdv = await this.silo.newMockBDVIncrease() - let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0) + let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, this.stem) const depositBdv = depositResult[1] // ----------------------- CONVERT ------------------------ this.result = await this.convert.connect(user2).convert( - // CALLDATA // amount, token ,account + // CALLDATA + // amount, token ,account ConvertEncoder.convertAntiLambdaToLambda('100', this.newSiloToken.address , userAddress), // STEMS [] - ['0'], + [this.stem], // AMOUNTS [] ['100'] ) + + // inital bdv: 1000000 + // new bdv: 1100000 + // grown stalk: 2e6 (newSiloToken has 1 seed). + // gspbdv = grown stalk / new bdv = 1.818181 + // stem = stemTip - gspbdv: 2 - 1.818181 = 0.181819 + this.newStem = 181819 }) it('Correctly updates deposit stats', async function () { - let deposit = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, 0); + let deposit = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, this.newStem); expect(deposit[0]).to.eq('100'); // deposit[0] = amount of tokens expect(deposit[1]).to.eq('1100000'); // deposit[1] = bdv }) - // it('Correctly updates totals', async function () { - // expect(await this.siloGetters.getTotalDeposited(this.newSiloToken.address)).to.equal('100'); - // expect(await this.siloGetters.getTotalDepositedBdv(this.newSiloToken.address)).to.eq('1100000'); - // // 100000 stalk added = 1 stalk/bdv for newSiloToken * 100000 bdv added from convert - // expect(await this.siloGetters.totalStalk()).to.equal('3100100'); - // }) - - // it('Emits events', async function () { - // await expect(this.result).to.emit(this.silo, 'RemoveDeposits').withArgs(userAddress, this.newSiloToken.address, [0], ['100'], '100', ['1000000']); - // await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, 0, '100', '1100000'); // last param = updated bdv - // await expect(this.result).to.emit(this.convert, 'Convert').withArgs(userAddress, this.newSiloToken.address, this.newSiloToken.address, '100', '100'); - // }) + it('Correctly updates totals', async function () { + expect(await this.siloGetters.getTotalDeposited(this.newSiloToken.address)).to.equal('100'); + expect(await this.siloGetters.getTotalDepositedBdv(this.newSiloToken.address)).to.eq('1100000'); + // 100000 stalk added = 1 stalk/bdv for newSiloToken * 100000 bdv added from convert + expect(await this.siloGetters.totalStalk()).to.equal('5100100'); + }) + + it('Emits events', async function () { + await expect(this.result).to.emit(this.silo, 'RemoveDeposits').withArgs(userAddress, this.newSiloToken.address, [this.stem], ['100'], '100', ['1000000']); + await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, this.newStem, '100', '1100000'); // last param = updated bdv + await expect(this.result).to.emit(this.convert, 'Convert').withArgs(userAddress, this.newSiloToken.address, this.newSiloToken.address, '100', '100'); + }) }) }); From 1625aa288c023148d2661740abd0a534a067c4be Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Sat, 23 Mar 2024 19:20:45 +0200 Subject: [PATCH 05/86] Re-add accidental deletions --- .../beanstalk/sun/SeasonFacet/Sun.sol | 4 +- .../libraries/Minting/LibWellMinting.sol | 53 ++++++++++++++- .../mocks/mockFacets/MockSeasonFacet.sol | 6 ++ protocol/test/Sun.test.js | 64 +++++++++++++++++-- protocol/test/WellMinting.test.js | 30 +++++++-- 5 files changed, 141 insertions(+), 16 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index 56841dc9dc..b6f39ddb5d 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -7,6 +7,8 @@ import {SafeCast} from "@openzeppelin/contracts/utils/SafeCast.sol"; import {LibFertilizer, SafeMath} from "contracts/libraries/LibFertilizer.sol"; import {LibSafeMath128} from "contracts/libraries/LibSafeMath128.sol"; import {Oracle, C} from "./Oracle.sol"; +import {Math} from "@openzeppelin/contracts/math/Math.sol"; +import {LibWellMinting} from "contracts/libraries/Minting/LibWellMinting.sol"; /** * @title Sun @@ -70,7 +72,7 @@ contract Sun is Oracle { // Below peg else { - setSoil(uint256(-deltaB)); + setSoilBelowPeg(deltaB); s.season.abovePeg = false; } } diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index f514227c52..b23e908450 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -14,6 +14,7 @@ import {LibWell} from "contracts/libraries/Well/LibWell.sol"; import {IBeanstalkWellFunction} from "contracts/interfaces/basin/IBeanstalkWellFunction.sol"; import {SignedSafeMath} from "@openzeppelin/contracts/math/SignedSafeMath.sol"; import {LibEthUsdOracle} from "contracts/libraries/Oracle/LibEthUsdOracle.sol"; +import {IInstantaneousPump} from "contracts/interfaces/basin/pumps/IInstantaneousPump.sol"; /** * @title Well Minting Oracle Library @@ -207,6 +208,56 @@ library LibWellMinting { } } + /** + * @dev Calculates the instantaneous delta B for a given Well address. + * @param well The address of the Well. + * @return deltaB The instantaneous delta B balance since the last `capture` call. + */ + function instantaneousDeltaB(address well) internal view returns + (int256, uint256[] memory, uint256[] memory) { + + AppStorage storage s = LibAppStorage.diamondStorage(); + Call[] memory pumps = IWell(well).pumps(); + // well address , data[] + try IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, C.BYTES_ZERO) returns (uint[] memory instReserves) { + // Get well tokens + IERC20[] memory tokens = IWell(well).tokens(); + + // Get ratios and bean index + ( + uint256[] memory ratios, + uint256 beanIndex, + bool success + ) = LibWell.getRatiosAndBeanIndex(tokens, block.timestamp.sub(s.season.timestamp)); + + // HANDLE FAILURE + // If the Bean reserve is less than the minimum, the minting oracle should be considered off. + if (instReserves[beanIndex] < C.WELL_MINIMUM_BEAN_BALANCE) { + return (0, new uint256[](0), new uint256[](0)); + } + + // If the USD Oracle oracle call fails, the minting oracle should be considered off. + if (!success) { + return (0, instReserves, new uint256[](0)); + } + + // Get well function + Call memory wellFunction = IWell(well).wellFunction(); + + // Delta B is the difference between the target Bean reserve at the peg price + // and the instantaneous Bean balance in the Well. + int256 deltaB = int256(IBeanstalkWellFunction(wellFunction.target).calcReserveAtRatioSwap( + instReserves, + beanIndex, + ratios, + wellFunction.data + )).sub(int256(instReserves[beanIndex])); + + return (deltaB, instReserves, ratios); + } + catch {} + } + // Remove in next BIP. function checkShouldTurnOnMinting(address well) internal view returns (bool) { AppStorage storage s = LibAppStorage.diamondStorage(); @@ -216,5 +267,5 @@ library LibWellMinting { } } return true; - } + } } diff --git a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol index c1a23cceb9..6f38220999 100644 --- a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol @@ -558,4 +558,10 @@ contract MockSeasonFacet is SeasonFacet { germ ); } + + function captureWellEInstantaneous(address well) external returns (int256 instDeltaB) { + (instDeltaB, ,) = LibWellMinting.instantaneousDeltaB(well); + s.season.timestamp = block.timestamp; + emit DeltaB(instDeltaB); + } } diff --git a/protocol/test/Sun.test.js b/protocol/test/Sun.test.js index 76e5b443f4..843b2708ff 100644 --- a/protocol/test/Sun.test.js +++ b/protocol/test/Sun.test.js @@ -9,6 +9,8 @@ const { setEthUsdChainlinkPrice, setWstethUsdPrice } = require('../utils/oracle. const { deployBasin } = require('../scripts/basin.js'); const ZERO_BYTES = ethers.utils.formatBytes32String('0x0') const { deployBasinV1_1Upgrade } = require('../scripts/basinV1_1.js'); +const { advanceTime } = require('../utils/helpers.js'); +const { deployMockWell, setReserves, deployMockBeanWell } = require('../utils/well.js'); let user, user2, owner; let userAddress, ownerAddress, user2Address; @@ -75,6 +77,8 @@ describe('Sun', function () { await c.multiFlowPump.update([toBean('10000'), to18('10')], 0x00); this.pump = c.multiFlowPump; + [this.well, this.wellFunction, this.pump] = await deployMockBeanWell(BEAN_ETH_WELL, WETH); + await this.season.siloSunrise(0) }) @@ -86,11 +90,61 @@ describe('Sun', function () { await revertToSnapshot(snapshotId) }) - it("delta B < 1", async function () { - this.result = await this.season.sunSunrise('-100', 8); - await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100'); + it("When deltaB < 0 it sets the soil to be the min of -twaDeltaB and -instantaneous deltaB", async function () { + // go fo forward 1800 blocks + await advanceTime(1800) + // set reserves to 2M Beans and 1000 Eth + // await this.well.setReserves([to6('2000000'), to18('1000')]) + await await this.well.setReserves([to6('2000000'), to18('1000')]); + await await this.well.setReserves([to6('2000000'), to18('1000')]); + // go forward 1800 blocks + await advanceTime(1800) + // send 0 eth to beanstalk + await user.sendTransaction({ + to: this.diamond.address, + value: 0 + }) + + // twaDeltaB = -100000000 + // instantaneousDeltaB = -585786437627 + // twaDeltaB, case ID + this.result = await this.season.sunSunrise('-100000000', 8); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000'); + await expect(await this.field.totalSoil()).to.be.equal('100000000'); }) + + it("When deltaB < 0 it sets the correct soil if the instantanious deltaB oracle fails", async function () { + // go fo forward 1800 blocks + await advanceTime(1800) + // set reserves to 1 Bean and 1 Eth + // If the Bean reserve is less than the minimum of 1000 beans, + // LibWellMinting.instantaneousDeltaB returns a deltaB of 0 + await this.well.setReserves([to6('1'), to18('1')]); + await this.well.setReserves([to6('1'), to18('1')]); + // go forward 1800 blocks + await advanceTime(1800) + // send 0 eth to beanstalk + await user.sendTransaction({ + to: this.diamond.address, + value: 0 + }) + + // twadeltaB, CASE ID + this.result = await this.season.sunSunrise('-100000000', 8); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000'); + await expect(await this.field.totalSoil()).to.be.equal('100000000'); + }) + + it("rewards more than type(uint128).max Soil below peg", async function () { + // Here, since we haven't changed the reserves the instantaneous deltaB is 0 + // And we maniuplate the twaDeltaB to be a number that is greater than type(uint128).max + // Because we consider that a value of 0 returned by the oracle to be a failure, we set the soil to be the twaDeltaB + // which is greater than type(uint128).max and therefore reverts + // twadeltaB, CASE ID + await expect(this.season.sunSunrise('-340282366920938463463374607431768211456', '8')).to.be.revertedWith('SafeCast: value doesn\'t fit in 128 bits'); + }) + it("delta B == 1", async function () { this.result = await this.season.sunSunrise('0', 8); await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '0'); @@ -365,10 +419,6 @@ describe('Sun', function () { it("rewards more than type(uint128).max/10000 to silo", async function () { await expect(this.season.siloSunrise('340282366920938463463374607431768211456')).to.be.revertedWith('SafeCast: value doesn\'t fit in 128 bits'); }) - - it("rewards more than type(uint128).max Soil below peg", async function () { - await expect(this.season.sunSunrise('-340282366920938463463374607431768211456', '0')).to.be.revertedWith('SafeCast: value doesn\'t fit in 128 bits'); - }) }) function viewGenericUint256Logs(logs) { diff --git a/protocol/test/WellMinting.test.js b/protocol/test/WellMinting.test.js index b9810ab855..c0d08f638a 100644 --- a/protocol/test/WellMinting.test.js +++ b/protocol/test/WellMinting.test.js @@ -53,11 +53,15 @@ describe('Well Minting', function () { }) }) - it("Captures", async function () { + it("Captures twa", async function () { expect(await this.season.callStatic.captureWellE(this.well.address)).to.be.equal('0') }) + + it("Captures instantaneous", async function () { + expect(await this.season.callStatic.captureWellEInstantaneous(this.well.address)).to.be.equal('0') + }) - it("Checks", async function () { + it("Checks twa", async function () { expect(await this.seasonGetter.poolDeltaB(this.well.address)).to.be.equal('0') }) @@ -74,11 +78,15 @@ describe('Well Minting', function () { }) }) - it("Captures a delta B > 0", async function () { + it("Captures a twa delta B > 0", async function () { expect(await this.season.callStatic.captureWellE(this.well.address)).to.be.equal('133789634067') }) + + it("Captures an instantaneous delta B > 0", async function () { + expect(await this.season.callStatic.captureWellEInstantaneous(this.well.address)).to.be.equal('207106781186') + }) - it("Checks a delta B > 0", async function () { + it("Checks a twa delta B > 0", async function () { expect(await this.seasonGetter.poolDeltaB(this.well.address)).to.be.equal('133789634067') }) }) @@ -94,11 +102,15 @@ describe('Well Minting', function () { }) }) - it("Captures a delta B < 0", async function () { + it("Captures a twa delta B < 0", async function () { expect(await this.season.callStatic.captureWellE(this.well.address)).to.be.equal('-225006447371') }) - it("Checks a delta B < 0", async function () { + it("Captures an instantaneous delta B < 0", async function () { + expect(await this.season.callStatic.captureWellEInstantaneous(this.well.address)).to.be.equal('-585786437627') + }) + + it("Checks a twa delta B < 0", async function () { expect(await this.seasonGetter.poolDeltaB(this.well.address)).to.be.equal('-225006447371') }) }) @@ -114,10 +126,14 @@ describe('Well Minting', function () { }) }) - it("Captures a Beans below min", async function () { + it("Captures a Beans below min twa", async function () { expect(await this.season.callStatic.captureWellE(this.well.address)).to.be.equal('0') }) + it("Captures a Beans below min instantaneous", async function () { + expect(await this.season.callStatic.captureWellEInstantaneous(this.well.address)).to.be.equal('0') + }) + it("Checks a Beans below min", async function () { expect(await this.seasonGetter.poolDeltaB(this.well.address)).to.be.equal('0') }) From 53d6b760b2f06f018862acf7259536df73d29ed9 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 11:04:26 +0200 Subject: [PATCH 06/86] dynamically calculate instDeltaB for all whitelisted wells --- .../contracts/beanstalk/sun/SeasonFacet/Sun.sol | 14 ++++++++++---- .../contracts/libraries/Minting/LibWellMinting.sol | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index b6f39ddb5d..f60b330bf5 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -8,7 +8,9 @@ import {LibFertilizer, SafeMath} from "contracts/libraries/LibFertilizer.sol"; import {LibSafeMath128} from "contracts/libraries/LibSafeMath128.sol"; import {Oracle, C} from "./Oracle.sol"; import {Math} from "@openzeppelin/contracts/math/Math.sol"; +import {SignedSafeMath} from "@openzeppelin/contracts/math/SignedSafeMath.sol"; import {LibWellMinting} from "contracts/libraries/Minting/LibWellMinting.sol"; +import {LibWhitelistedTokens} from "contracts/libraries/Silo/LibWhitelistedTokens.sol"; /** * @title Sun @@ -19,6 +21,7 @@ contract Sun is Oracle { using SafeCast for uint256; using SafeMath for uint256; using LibSafeMath128 for uint128; + using SignedSafeMath for int256; /// @dev When Fertilizer is Active, it receives 1/3 of new Bean mints. uint256 private constant FERTILIZER_DENOMINATOR = 3; @@ -236,10 +239,13 @@ contract Sun is Oracle { */ function setSoilBelowPeg(int256 twaDeltaB) internal { - // Calculate deltaB from instantaneous reserves. - // NOTE: deltaB is calculated only from the Bean:ETH Well at this time. - // If more wells are added, this will need to be updated. - (int256 instDeltaB, ,) = LibWellMinting.instantaneousDeltaB(C.BEAN_ETH_WELL); + // Calculate deltaB from instantaneous reserves of all whitelisted Wells. + int256 instDeltaB; + address[] memory tokens = LibWhitelistedTokens.getWhitelistedWellLpTokens(); + for (uint256 i = 0; i < tokens.length; i++) { + (int256 wellInstDeltaB, ,) = LibWellMinting.instantaneousDeltaB(tokens[i]); + instDeltaB = instDeltaB.add(wellInstDeltaB); + } // If the inst delta b is 0 it means that the oracle failed so the twa delta b is used. uint256 newSoil = instDeltaB == 0 ? uint256(-twaDeltaB) : Math.min(uint256(-twaDeltaB), uint256(-instDeltaB)); diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index b23e908450..31d795045c 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -208,7 +208,7 @@ library LibWellMinting { } } - /** + /** * @dev Calculates the instantaneous delta B for a given Well address. * @param well The address of the Well. * @return deltaB The instantaneous delta B balance since the last `capture` call. From e89d068ea2dcf89c9865a9c6de8410e59e4c12c1 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 11:27:12 +0200 Subject: [PATCH 07/86] omit newsoil var declaration --- protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index f60b330bf5..744bdae6d3 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -251,7 +251,7 @@ contract Sun is Oracle { uint256 newSoil = instDeltaB == 0 ? uint256(-twaDeltaB) : Math.min(uint256(-twaDeltaB), uint256(-instDeltaB)); // Set new soil. - setSoil(newSoil); + setSoil(instDeltaB == 0 ? uint256(-twaDeltaB) : Math.min(uint256(-twaDeltaB), uint256(-instDeltaB))); } /** From 2021d0089abed177130480cb3679216e8e69b014 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 11:31:37 +0200 Subject: [PATCH 08/86] get ratios for instdeltab from instantenious usd oracle --- protocol/contracts/libraries/Minting/LibWellMinting.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index 31d795045c..bbc582da17 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -228,7 +228,7 @@ library LibWellMinting { uint256[] memory ratios, uint256 beanIndex, bool success - ) = LibWell.getRatiosAndBeanIndex(tokens, block.timestamp.sub(s.season.timestamp)); + ) = LibWell.getRatiosAndBeanIndex(tokens); // HANDLE FAILURE // If the Bean reserve is less than the minimum, the minting oracle should be considered off. From 1122ce882c48b2410ad6071f64a467579d805afd Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 12:55:48 +0200 Subject: [PATCH 09/86] refactor libwellminting --- .../libraries/Minting/LibWellMinting.sol | 139 +++++++----------- 1 file changed, 55 insertions(+), 84 deletions(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index bbc582da17..3c6af0f007 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -103,12 +103,6 @@ library LibWellMinting { function initializeOracle(address well) internal { AppStorage storage s = LibAppStorage.diamondStorage(); - // Given Multi Flow Pump V 1.0 isn't resistant to large changes in balance, - // minting in the Bean:Eth Well needs to be turned off upon migration. - if (!checkShouldTurnOnMinting(well)) { - return; - } - // If pump has not been initialized for `well`, `readCumulativeReserves` will revert. // Need to handle failure gracefully, so Sunrise does not revert. Call[] memory pumps = IWell(well).pumps(); @@ -154,6 +148,53 @@ library LibWellMinting { s.wellOracleSnapshots[well] ); } + + /** + * @dev Calculates the deltaB for a given Well address + * and returns the deltaB, snapshot, reserves and ratios. + * @param well The address of the Well. + * @return deltaB The instantaneous delta B balance since the last `capture` call. + */ + function getDeltaBInfo(address well, uint[] memory reserves, bytes memory snapshot, uint256 lookback + ) internal view returns (int256, bytes memory, uint256[] memory, uint256[] memory) { + + // get well tokens + IERC20[] memory tokens = IWell(well).tokens(); + ( + uint256[] memory ratios, + uint256 beanIndex, + bool success + ) = LibWell.getRatiosAndBeanIndex(tokens, lookback); + + // If the Bean reserve is less than the minimum, the minting oracle should be considered off. + if (reserves[beanIndex] < C.WELL_MINIMUM_BEAN_BALANCE) { + return (0, snapshot, new uint256[](0), new uint256[](0)); + } + + // If the USD Oracle oracle call fails, the minting oracle should be considered off. + if (!success) { + return (0, snapshot, reserves, new uint256[](0)); + } + + int256 deltaB = calculateDeltaBAtBeanIndex(well, reserves, ratios, beanIndex); + + return (deltaB, snapshot, reserves, ratios); + } + + /** + * @dev Calculates the delta B at a given Bean index for a given Well address + * based on the current well reserves, well ratios and well function. + */ + function calculateDeltaBAtBeanIndex(address well, uint[] memory reserves, uint256[] memory ratios, uint256 beanIndex + ) internal view returns (int256) { + Call memory wellFunction = IWell(well).wellFunction(); + return int256(IBeanstalkWellFunction(wellFunction.target).calcReserveAtRatioSwap( + reserves, + beanIndex, + ratios, + wellFunction.data + )).sub(int256(reserves[beanIndex])); + } /** * @dev Calculates the time weighted average delta B since the input snapshot for @@ -173,34 +214,8 @@ library LibWellMinting { uint40(s.season.timestamp), pumps[0].data ) returns (uint[] memory twaReserves, bytes memory snapshot) { - IERC20[] memory tokens = IWell(well).tokens(); - ( - uint256[] memory ratios, - uint256 beanIndex, - bool success - ) = LibWell.getRatiosAndBeanIndex(tokens, block.timestamp.sub(s.season.timestamp)); - - // If the Bean reserve is less than the minimum, the minting oracle should be considered off. - if (twaReserves[beanIndex] < C.WELL_MINIMUM_BEAN_BALANCE) { - return (0, snapshot, new uint256[](0), new uint256[](0)); - } - - // If the USD Oracle oracle call fails, the minting oracle should be considered off. - if (!success) { - return (0, snapshot, twaReserves, new uint256[](0)); - } - - Call memory wellFunction = IWell(well).wellFunction(); - // Delta B is the difference between the target Bean reserve at the peg price - // and the time weighted average Bean balance in the Well. - int256 deltaB = int256(IBeanstalkWellFunction(wellFunction.target).calcReserveAtRatioSwap( - twaReserves, - beanIndex, - ratios, - wellFunction.data - )).sub(int256(twaReserves[beanIndex])); - - return (deltaB, snapshot, twaReserves, ratios); + // well, reserves, snapshot lookback + return (getDeltaBInfo(well, twaReserves, snapshot, block.timestamp.sub(s.season.timestamp))); } catch { // if the pump fails, return all 0s to avoid the sunrise reverting. @@ -213,59 +228,15 @@ library LibWellMinting { * @param well The address of the Well. * @return deltaB The instantaneous delta B balance since the last `capture` call. */ - function instantaneousDeltaB(address well) internal view returns - (int256, uint256[] memory, uint256[] memory) { + function instantaneousDeltaB(address well) internal view returns (int256, uint256[] memory, uint256[] memory) { AppStorage storage s = LibAppStorage.diamondStorage(); Call[] memory pumps = IWell(well).pumps(); - // well address , data[] - try IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, C.BYTES_ZERO) returns (uint[] memory instReserves) { - // Get well tokens - IERC20[] memory tokens = IWell(well).tokens(); - - // Get ratios and bean index - ( - uint256[] memory ratios, - uint256 beanIndex, - bool success - ) = LibWell.getRatiosAndBeanIndex(tokens); - - // HANDLE FAILURE - // If the Bean reserve is less than the minimum, the minting oracle should be considered off. - if (instReserves[beanIndex] < C.WELL_MINIMUM_BEAN_BALANCE) { - return (0, new uint256[](0), new uint256[](0)); - } - - // If the USD Oracle oracle call fails, the minting oracle should be considered off. - if (!success) { - return (0, instReserves, new uint256[](0)); - } - - // Get well function - Call memory wellFunction = IWell(well).wellFunction(); - - // Delta B is the difference between the target Bean reserve at the peg price - // and the instantaneous Bean balance in the Well. - int256 deltaB = int256(IBeanstalkWellFunction(wellFunction.target).calcReserveAtRatioSwap( - instReserves, - beanIndex, - ratios, - wellFunction.data - )).sub(int256(instReserves[beanIndex])); - - return (deltaB, instReserves, ratios); - } - catch {} + // well address , data[] + uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, C.BYTES_ZERO); + // well, reserves, snapshot, lookback + (int256 deltaB, , ,uint256[] memory ratios) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); + return (deltaB, instReserves, ratios); } - // Remove in next BIP. - function checkShouldTurnOnMinting(address well) internal view returns (bool) { - AppStorage storage s = LibAppStorage.diamondStorage(); - if (well == C.BEAN_ETH_WELL) { - if (s.season.current < s.season.beanEthStartMintingSeason) { - return false; - } - } - return true; - } } From bc246db08b4eec11003c88e78f19c6726db3a231 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 13:25:02 +0200 Subject: [PATCH 10/86] add convertParams struct --- .../contracts/beanstalk/silo/ConvertFacet.sol | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/protocol/contracts/beanstalk/silo/ConvertFacet.sol b/protocol/contracts/beanstalk/silo/ConvertFacet.sol index bb39b22e26..50c86e41de 100644 --- a/protocol/contracts/beanstalk/silo/ConvertFacet.sol +++ b/protocol/contracts/beanstalk/silo/ConvertFacet.sol @@ -26,6 +26,14 @@ contract ConvertFacet is ReentrancyGuard { using SafeCast for uint256; using LibSafeMath32 for uint32; + struct convertParams { + address toToken; + address fromToken; + uint256 grownStalk; + address account; + bool decreaseBDV; + } + event Convert( address indexed account, address fromToken, @@ -75,39 +83,36 @@ contract ConvertFacet is ReentrancyGuard { nonReentrant returns (int96 toStem, uint256 fromAmount, uint256 toAmount, uint256 fromBdv, uint256 toBdv) { - address toToken; address fromToken; uint256 grownStalk; address account; bool decreaseBDV; - - (toToken, fromToken, toAmount, fromAmount, account, decreaseBDV) = LibConvert.convert(convertData); + convertParams memory cp; + (cp.toToken, cp.fromToken, toAmount, fromAmount, cp.account, cp.decreaseBDV) = LibConvert.convert(convertData); require(fromAmount > 0, "Convert: From amount is 0."); - require(fromAmount > 0, "Convert: From amount is 0."); - // Replace account with msg.sender if no account is specified. - if(account == address(0)) account = msg.sender; + if(cp.account == address(0)) cp.account = msg.sender; - LibSilo._mow(account, fromToken); + LibSilo._mow(cp.account, cp.fromToken); // If the fromToken and toToken are different, mow the toToken as well. - if (fromToken != toToken) LibSilo._mow(account, toToken); + if (cp.fromToken != cp.toToken) LibSilo._mow(cp.account, cp.toToken); // Withdraw the tokens from the deposit. - (grownStalk, fromBdv) = _withdrawTokens( - fromToken, + (cp.grownStalk, fromBdv) = _withdrawTokens( + cp.fromToken, stems, amounts, fromAmount, - account + cp.account ); // Calculate the bdv of the new deposit. - uint256 newBdv = LibTokenSilo.beanDenominatedValue(toToken, toAmount); + uint256 newBdv = LibTokenSilo.beanDenominatedValue(cp.toToken, toAmount); // If `decreaseBDV` flag is not enabled, set toBDV to the max of the two bdvs. - toBdv = (newBdv > fromBdv || decreaseBDV) ? newBdv : fromBdv; + toBdv = (newBdv > fromBdv || cp.decreaseBDV) ? newBdv : fromBdv; - toStem = _depositTokensForConvert(toToken, toAmount, toBdv, grownStalk, account); - emit Convert(account, fromToken, toToken, fromAmount, toAmount); + toStem = _depositTokensForConvert(cp.toToken, toAmount, toBdv, cp.grownStalk, cp.account); + emit Convert(cp.account, cp.fromToken, cp.toToken, fromAmount, toAmount); } /** From 3254dd6c7a72d6a342afb46f8df4340ba2da4372 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 13:34:56 +0200 Subject: [PATCH 11/86] refactor convert if ladder --- .../libraries/Convert/LibConvert.sol | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/protocol/contracts/libraries/Convert/LibConvert.sol b/protocol/contracts/libraries/Convert/LibConvert.sol index 7795fe8a13..fe8f982a2e 100644 --- a/protocol/contracts/libraries/Convert/LibConvert.sol +++ b/protocol/contracts/libraries/Convert/LibConvert.sol @@ -48,8 +48,11 @@ library LibConvert { // if (kind == LibConvertData.ConvertKind.BEANS_TO_CURVE_LP) { // (tokenOut, tokenIn, amountOut, amountIn) = LibCurveConvert // .convertBeansToLP(convertData); - if (kind == LibConvertData.ConvertKind.CURVE_LP_TO_BEANS) { - (tokenOut, tokenIn, amountOut, amountIn) = LibCurveConvert + if (kind == LibConvertData.ConvertKind.BEANS_TO_WELL_LP) { + (tokenOut, tokenIn, amountOut, amountIn) = LibWellConvert + .convertBeansToLP(convertData); + } else if (kind == LibConvertData.ConvertKind.WELL_LP_TO_BEANS) { + (tokenOut, tokenIn, amountOut, amountIn) = LibWellConvert .convertLPToBeans(convertData); } else if (kind == LibConvertData.ConvertKind.UNRIPE_BEANS_TO_UNRIPE_LP) { (tokenOut, tokenIn, amountOut, amountIn) = LibUnripeConvert @@ -60,18 +63,12 @@ library LibConvert { } else if (kind == LibConvertData.ConvertKind.LAMBDA_LAMBDA) { (tokenOut, tokenIn, amountOut, amountIn) = LibLambdaConvert .convert(convertData); - } else if (kind == LibConvertData.ConvertKind.BEANS_TO_WELL_LP) { - (tokenOut, tokenIn, amountOut, amountIn) = LibWellConvert - .convertBeansToLP(convertData); - } else if (kind == LibConvertData.ConvertKind.WELL_LP_TO_BEANS) { - (tokenOut, tokenIn, amountOut, amountIn) = LibWellConvert - .convertLPToBeans(convertData); - } else if (kind == LibConvertData.ConvertKind.UNRIPE_TO_RIPE) { - (tokenOut, tokenIn, amountOut, amountIn) = LibChopConvert - .convertUnripeToRipe(convertData); } else if (kind == LibConvertData.ConvertKind.ANTI_LAMBDA_LAMBDA) { - (tokenOut, tokenIn, amountOut, amountIn, account, decreaseBDV) = LibLambdaConvert + (tokenOut, tokenIn, amountOut, amountIn, account, decreaseBDV) = LibLambdaConvert .antiConvert(convertData); + } else if (kind == LibConvertData.ConvertKind.CURVE_LP_TO_BEANS) { + (tokenOut, tokenIn, amountOut, amountIn) = LibCurveConvert + .convertLPToBeans(convertData); } else { revert("Convert: Invalid payload"); } From 666cf7e23f84b1a963a9b48e89a564d642b43ad0 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 13:37:15 +0200 Subject: [PATCH 12/86] remove libconvertdata spaces --- protocol/contracts/libraries/Convert/LibConvertData.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/contracts/libraries/Convert/LibConvertData.sol b/protocol/contracts/libraries/Convert/LibConvertData.sol index 2cb019085a..eccdefc3f9 100644 --- a/protocol/contracts/libraries/Convert/LibConvertData.sol +++ b/protocol/contracts/libraries/Convert/LibConvertData.sol @@ -74,9 +74,9 @@ library LibConvertData { function antiLambdaConvert(bytes memory self) internal pure - returns (uint256 amount, address token , address account, bool decreaseBDV) + returns (uint256 amount, address token, address account, bool decreaseBDV) { - (, amount, token , account) = abi.decode(self, (ConvertKind, uint256, address , address)); + (, amount, token, account) = abi.decode(self, (ConvertKind, uint256, address , address)); decreaseBDV = true; } } From 14eced6d318c0e51e1d7e553e6fb775dacfae4e3 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 13:40:39 +0200 Subject: [PATCH 13/86] fix comment on libstrings --- protocol/contracts/libraries/LibStrings.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/libraries/LibStrings.sol b/protocol/contracts/libraries/LibStrings.sol index 6f6ed84776..e5c8fa5d7d 100644 --- a/protocol/contracts/libraries/LibStrings.sol +++ b/protocol/contracts/libraries/LibStrings.sol @@ -93,7 +93,7 @@ library LibStrings { pure returns (string memory) { - // Cast to uint256 to be compatible with toString + // Cast to uint256 to be compatible with toString string memory numString = toString(uint256(number)); // If the number has fewer than 6 decimals, add trailing zeros From 65242c59607f5842876e25801def3344aea71071 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 13:57:15 +0200 Subject: [PATCH 14/86] fert image comment fixes --- .../tokens/Fertilizer/FertilizerImage.sol | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol b/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol index 48542543d1..7a5dbe276e 100644 --- a/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol +++ b/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol @@ -3,7 +3,6 @@ pragma solidity ^0.7.6; pragma experimental ABIEncoderV2; -import "contracts/libraries/LibStrings.sol"; import {LibStrings} from "contracts/libraries/LibStrings.sol"; import {LibBytes64} from "contracts/libraries/LibBytes64.sol"; @@ -58,19 +57,19 @@ contract FertilizerImage { // END OF SVG /** - * @dev generateImageSvg assembles the needed components for the Fertilizer svg - * For use in the on-chain json fertilizer metadata - * @param _id - the id of the Fertilizer - * @param bpfRemaining - the bpfRemaining of the Fertilizer - * @return imageUri - the image URI representation of the Fertilizer + * @dev generateImageSvg assembles the needed components for the Fertilizer svg + * For use in the on-chain json fertilizer metadata + * @param _id - the id of the Fertilizer + * @param bpfRemaining - the bpfRemaining of the Fertilizer + * @return imageUri - the image URI representation of the Fertilizer */ function generateImageSvg(uint256 _id, uint128 bpfRemaining) internal view returns (string memory) { return string( abi.encodePacked( '', // SVG HEADER - BASE_SVG_START, // BASE SVG START + BASE_SVG_START, getFertilizerStatusSvg(_id, bpfRemaining), // FERT_SVG_TOP (available, active) - BASE_SVG_END, // BASE SVG END + BASE_SVG_END, '', // PRE NUMBER FOR BPF REMAINING LibStrings.formatUintWith6DecimalsTo2(bpfRemaining), // BPF_REMAINING with 2 decimal places " BPF Remaining " // END OF SVG @@ -79,10 +78,10 @@ contract FertilizerImage { } /** - * @dev Returns the correct svg top for the Fertilizer status based on the bpfRemaining. - * @param _id - the id of the Fertilizer - * @param bpfRemaining - the bpfRemaining of the Fertilizer - * @return fertilizerStatusSvg an svg top for the correct Fertilizer status + * @dev Returns the correct svg top for the Fertilizer status based on the bpfRemaining. + * @param _id - the id of the Fertilizer + * @param bpfRemaining - the bpfRemaining of the Fertilizer + * @return fertilizerStatusSvg an svg top for the correct Fertilizer status */ function getFertilizerStatusSvg(uint256 _id, uint128 bpfRemaining) internal view returns (string memory) { From 696b96bfc56eee725a171aa699c7fc99db6440d2 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 14:58:38 +0200 Subject: [PATCH 15/86] change function visibility on internalizer --- protocol/contracts/tokens/Fertilizer/Internalizer.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/contracts/tokens/Fertilizer/Internalizer.sol b/protocol/contracts/tokens/Fertilizer/Internalizer.sol index 73626343a4..ac31adf1ae 100644 --- a/protocol/contracts/tokens/Fertilizer/Internalizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Internalizer.sol @@ -106,11 +106,11 @@ contract Internalizer is OwnableUpgradeable, ReentrancyGuardUpgradeable, Fertili } } - function name() public pure returns (string memory) { + function name() external pure returns (string memory) { return "Fertilizer"; } - function symbol() public pure returns (string memory) { + function symbol() external pure returns (string memory) { return "FERT"; } From bcd391bee4978e679df5a1ff39a17562080fcecf Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 28 Mar 2024 15:09:18 +0200 Subject: [PATCH 16/86] sun.test changes --- protocol/test/Sun.test.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/protocol/test/Sun.test.js b/protocol/test/Sun.test.js index 843b2708ff..a8fcaaefe7 100644 --- a/protocol/test/Sun.test.js +++ b/protocol/test/Sun.test.js @@ -90,11 +90,10 @@ describe('Sun', function () { await revertToSnapshot(snapshotId) }) - it("When deltaB < 0 it sets the soil to be the min of -twaDeltaB and -instantaneous deltaB", async function () { - // go fo forward 1800 blocks + it("When deltaB < 0 it sets the soil to be the min of -twaDeltaB and -instantaneous deltaB (-twaDeltaB < -instDeltaB)", async function () { + // go forward 1800 blocks await advanceTime(1800) // set reserves to 2M Beans and 1000 Eth - // await this.well.setReserves([to6('2000000'), to18('1000')]) await await this.well.setReserves([to6('2000000'), to18('1000')]); await await this.well.setReserves([to6('2000000'), to18('1000')]); // go forward 1800 blocks @@ -113,8 +112,30 @@ describe('Sun', function () { await expect(await this.field.totalSoil()).to.be.equal('100000000'); }) + it("When deltaB < 0 it sets the soil to be the min of -twaDeltaB and -instantaneous deltaB (-twaDeltaB > -instDeltaB)", async function () { + // go forward 1800 blocks + await advanceTime(1800) + // set reserves to 2M Beans and 1000 Eth + await await this.well.setReserves([to6('2000000'), to18('1000')]); + await await this.well.setReserves([to6('2000000'), to18('1000')]); + // go forward 1800 blocks + await advanceTime(1800) + // send 0 eth to beanstalk + await user.sendTransaction({ + to: this.diamond.address, + value: 0 + }) + + // twaDeltaB = -100000000000000000 + // instantaneousDeltaB = -585786437627 + // twaDeltaB, case ID + this.result = await this.season.sunSunrise('-100000000000000000', 8); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000000000000'); + await expect(await this.field.totalSoil()).to.be.equal('100000000000000000'); + }) + - it("When deltaB < 0 it sets the correct soil if the instantanious deltaB oracle fails", async function () { + it("When deltaB < 0 it sets the correct soil if the instantaneous deltaB oracle fails", async function () { // go fo forward 1800 blocks await advanceTime(1800) // set reserves to 1 Bean and 1 Eth From f9726afd1b538bbbb181791aa9c98ce1fa4dffb7 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 29 Mar 2024 16:10:52 +0200 Subject: [PATCH 17/86] sun and sun test fixes --- .../contracts/beanstalk/sun/SeasonFacet/Sun.sol | 5 ++--- protocol/test/Sun.test.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index 744bdae6d3..49b4307ba0 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -247,11 +247,10 @@ contract Sun is Oracle { instDeltaB = instDeltaB.add(wellInstDeltaB); } - // If the inst delta b is 0 it means that the oracle failed so the twa delta b is used. - uint256 newSoil = instDeltaB == 0 ? uint256(-twaDeltaB) : Math.min(uint256(-twaDeltaB), uint256(-instDeltaB)); - // Set new soil. + // If the inst delta b is 0 it means that the oracle failed so the twa delta b is used. setSoil(instDeltaB == 0 ? uint256(-twaDeltaB) : Math.min(uint256(-twaDeltaB), uint256(-instDeltaB))); + } /** diff --git a/protocol/test/Sun.test.js b/protocol/test/Sun.test.js index a8fcaaefe7..d121ace3b6 100644 --- a/protocol/test/Sun.test.js +++ b/protocol/test/Sun.test.js @@ -93,6 +93,8 @@ describe('Sun', function () { it("When deltaB < 0 it sets the soil to be the min of -twaDeltaB and -instantaneous deltaB (-twaDeltaB < -instDeltaB)", async function () { // go forward 1800 blocks await advanceTime(1800) + // whitelist well to be included in the instantaneous deltaB calculation + await this.silo.mockWhitelistToken(BEAN_ETH_WELL, this.silo.interface.getSighash("mockBDV(uint256 amount)"), "10000", "1"); // set reserves to 2M Beans and 1000 Eth await await this.well.setReserves([to6('2000000'), to18('1000')]); await await this.well.setReserves([to6('2000000'), to18('1000')]); @@ -115,6 +117,9 @@ describe('Sun', function () { it("When deltaB < 0 it sets the soil to be the min of -twaDeltaB and -instantaneous deltaB (-twaDeltaB > -instDeltaB)", async function () { // go forward 1800 blocks await advanceTime(1800) + // whitelist well to be included in the instantaneous deltaB calculation + await this.silo.mockWhitelistToken(BEAN_ETH_WELL, this.silo.interface.getSighash("mockBDV(uint256 amount)"), "10000", "1"); + // set reserves to 2M Beans and 1000 Eth await await this.well.setReserves([to6('2000000'), to18('1000')]); await await this.well.setReserves([to6('2000000'), to18('1000')]); @@ -129,15 +134,17 @@ describe('Sun', function () { // twaDeltaB = -100000000000000000 // instantaneousDeltaB = -585786437627 // twaDeltaB, case ID - this.result = await this.season.sunSunrise('-100000000000000000', 8); - await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000000000000'); - await expect(await this.field.totalSoil()).to.be.equal('100000000000000000'); + this.result = await this.season.sunSunrise('-585786437627', 8); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '585786437627'); + await expect(await this.field.totalSoil()).to.be.equal('585786437627'); }) it("When deltaB < 0 it sets the correct soil if the instantaneous deltaB oracle fails", async function () { // go fo forward 1800 blocks await advanceTime(1800) + // whitelist well to be included in the instantaneous deltaB calculation + await this.silo.mockWhitelistToken(BEAN_ETH_WELL, this.silo.interface.getSighash("mockBDV(uint256 amount)"), "10000", "1"); // set reserves to 1 Bean and 1 Eth // If the Bean reserve is less than the minimum of 1000 beans, // LibWellMinting.instantaneousDeltaB returns a deltaB of 0 From d0b040d3c1942210b0ed0e35f3794fdfb7dd303f Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 29 Mar 2024 16:19:59 +0200 Subject: [PATCH 18/86] remove unused return parameters from instantaneousDeltaB() --- protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol | 2 +- protocol/contracts/libraries/Minting/LibWellMinting.sol | 6 +++--- protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index 49b4307ba0..e4ab138c2c 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -243,7 +243,7 @@ contract Sun is Oracle { int256 instDeltaB; address[] memory tokens = LibWhitelistedTokens.getWhitelistedWellLpTokens(); for (uint256 i = 0; i < tokens.length; i++) { - (int256 wellInstDeltaB, ,) = LibWellMinting.instantaneousDeltaB(tokens[i]); + int256 wellInstDeltaB = LibWellMinting.instantaneousDeltaB(tokens[i]); instDeltaB = instDeltaB.add(wellInstDeltaB); } diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index 3c6af0f007..13e6adda72 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -228,15 +228,15 @@ library LibWellMinting { * @param well The address of the Well. * @return deltaB The instantaneous delta B balance since the last `capture` call. */ - function instantaneousDeltaB(address well) internal view returns (int256, uint256[] memory, uint256[] memory) { + function instantaneousDeltaB(address well) internal view returns (int256) { AppStorage storage s = LibAppStorage.diamondStorage(); Call[] memory pumps = IWell(well).pumps(); // well address , data[] uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, C.BYTES_ZERO); // well, reserves, snapshot, lookback - (int256 deltaB, , ,uint256[] memory ratios) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); - return (deltaB, instReserves, ratios); + (int256 deltaB, , ,) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); + return (deltaB); } } diff --git a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol index 6f38220999..8c87b160bd 100644 --- a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol @@ -560,7 +560,7 @@ contract MockSeasonFacet is SeasonFacet { } function captureWellEInstantaneous(address well) external returns (int256 instDeltaB) { - (instDeltaB, ,) = LibWellMinting.instantaneousDeltaB(well); + instDeltaB = LibWellMinting.instantaneousDeltaB(well); s.season.timestamp = block.timestamp; emit DeltaB(instDeltaB); } From 892d9b6e7b102d4e55c18acc2dad4e4dfdd27c27 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 29 Mar 2024 16:34:49 +0200 Subject: [PATCH 19/86] fix broken chop convert test --- protocol/contracts/libraries/Convert/LibConvert.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protocol/contracts/libraries/Convert/LibConvert.sol b/protocol/contracts/libraries/Convert/LibConvert.sol index fe8f982a2e..010ce70b83 100644 --- a/protocol/contracts/libraries/Convert/LibConvert.sol +++ b/protocol/contracts/libraries/Convert/LibConvert.sol @@ -60,6 +60,9 @@ library LibConvert { } else if (kind == LibConvertData.ConvertKind.UNRIPE_LP_TO_UNRIPE_BEANS) { (tokenOut, tokenIn, amountOut, amountIn) = LibUnripeConvert .convertLPToBeans(convertData); + } else if (kind == LibConvertData.ConvertKind.UNRIPE_TO_RIPE) { + (tokenOut, tokenIn, amountOut, amountIn) = LibChopConvert + .convertUnripeToRipe(convertData); } else if (kind == LibConvertData.ConvertKind.LAMBDA_LAMBDA) { (tokenOut, tokenIn, amountOut, amountIn) = LibLambdaConvert .convert(convertData); From d030199033a25402c81bf82241c5fe590074acd9 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Sat, 30 Mar 2024 20:39:42 +0200 Subject: [PATCH 20/86] init chop rate change --- .../contracts/beanstalk/barn/UnripeFacet.sol | 15 +++++++-------- protocol/contracts/libraries/LibUnripe.sol | 17 +++++++++++++++-- protocol/test/Unripe.test.js | 19 +++++++++++++------ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index b7a3e5cb3e..f00980acc6 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -19,6 +19,7 @@ import {ReentrancyGuard} from "contracts/beanstalk/ReentrancyGuard.sol"; import {LibLockedUnderlying} from "contracts/libraries/LibLockedUnderlying.sol"; import {LibChop} from "contracts/libraries/LibChop.sol"; import {LibBarnRaise} from "contracts/libraries/LibBarnRaise.sol"; +import "hardhat/console.sol"; /** * @title UnripeFacet @@ -82,6 +83,10 @@ contract UnripeFacet is ReentrancyGuard { LibTransfer.From fromMode, LibTransfer.To toMode ) external payable nonReentrant returns (uint256) { + console.log("///////////////// UnripeFacet.chop() ///////////////////"); + console.log("Parameters"); + console.log("unripeToken: ", unripeToken); + console.log("amount: ", amount); // burn the token from the msg.sender address uint256 supply = IBean(unripeToken).totalSupply(); amount = LibTransfer.burnToken(IBean(unripeToken), amount, msg.sender, fromMode); @@ -96,6 +101,7 @@ contract UnripeFacet is ReentrancyGuard { IERC20(underlyingToken).sendToken(underlyingAmount, msg.sender, toMode); // emit the event emit Chop(msg.sender, unripeToken, amount, underlyingAmount); + console.log("\n\n/////////////////////////////////////// END CHOP ///////////////////////////////////////"); return underlyingAmount; } @@ -157,6 +163,7 @@ contract UnripeFacet is ReentrancyGuard { * @return penalty The current penalty for converting unripe --> ripe */ function getPenalty(address unripeToken) external view returns (uint256 penalty) { + // token // amount = 1e6 = 1 unripe token return getPenalizedUnderlying(unripeToken, LibUnripe.DECIMALS); } @@ -175,14 +182,6 @@ contract UnripeFacet is ReentrancyGuard { LibUnripe._getPenalizedUnderlying(unripeToken, amount, IBean(unripeToken).totalSupply()); } - function _getPenalizedUnderlying( - address unripeToken, - uint256 amount, - uint256 supply - ) public view returns (uint256 redeem) { - return LibUnripe._getPenalizedUnderlying(unripeToken, amount, supply); - } - /** * @notice Returns whether a token is an Unripe Token. * @param unripeToken The token address to check. diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 54f0646804..e9184ec202 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -12,6 +12,7 @@ import {LibWell} from "./Well/LibWell.sol"; import {Call, IWell} from "contracts/interfaces/basin/IWell.sol"; import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; +import "hardhat/console.sol"; /** * @title LibUnripe @@ -146,7 +147,11 @@ library LibUnripe { uint256 supply ) internal view returns (uint256 redeem) { require(isUnripe(unripeToken), "not vesting"); - uint256 sharesBeingRedeemed = getRecapPaidPercentAmount(amount); + // uint256 sharesBeingRedeemed = getRecapPaidPercentAmount(amount); + uint256 sharesBeingRedeemed = amount; + console.log("//////////////////// LibUnripe._getPenalizedUnderlying() ////////////////////"); + console.log("NEW sharesBeingRedeemed: ", sharesBeingRedeemed); + console.log("THIS SHOULD BE THE SAME AS THE AMOUNT PASSED IN"); redeem = _getUnderlying(unripeToken, sharesBeingRedeemed, supply); } @@ -235,6 +240,14 @@ library LibUnripe { uint256 supply ) internal view returns (uint256 redeem) { AppStorage storage s = LibAppStorage.diamondStorage(); - redeem = s.u[unripeToken].balanceOfUnderlying.mul(amount).div(supply); + //* @param balanceOfUnderlying The number of Tokens underlying the Unripe Tokens (redemption pool). + // redeem = amount * ( balanceOfUnderlying / supply ) ^ 2 + console.log("//////////////////// LibUnripe._getUnderlying() ////////////////////"); + console.log("unripeToken: ", unripeToken); + console.log("unripe shares redeemed: ", amount); + console.log("unripe supply: ", supply); + console.log("unripe balanceOfUnderlying: ", s.u[unripeToken].balanceOfUnderlying); + redeem = (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2); + console.log("FINAL REDEEM OF RIPE FROM UNRIPE AFTER SQUARING: ", redeem); } } diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index b72df042c2..d5b51b821c 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -71,11 +71,16 @@ describe('Unripe', function () { }) it('getters', async function () { + // getUnderlyingPerUnripeToken Returns the amount of Ripe Tokens that underly a single Unripe Token. + // no connection with penalty params expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) + // no penalty expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0')) expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal('0') expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) + // getUnderlying Returns the amount of Ripe Tokens that underly a given amount of Unripe Tokens. + // Does NOT include the penalty associated with the percent of Sprouts that are Rinsable or Rinsed. expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal('0') @@ -119,6 +124,8 @@ describe('Unripe', function () { }) }) + ///////////////////////// CHOP ///////////////////////// + describe('chop', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( @@ -131,7 +138,7 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100099') + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.999')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) @@ -143,7 +150,7 @@ describe('Unripe', function () { it('changes balaces', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.001')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.01')) expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')) expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.999')) }) @@ -153,7 +160,7 @@ describe('Unripe', function () { user.address, UNRIPE_BEAN, to6('1'), - to6('0.001') + to6('0.01') ) }) }) @@ -177,7 +184,7 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100099') + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.999')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) @@ -189,7 +196,7 @@ describe('Unripe', function () { it('changes balaces', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.001')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.01')) expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')) expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.999')) }) @@ -199,7 +206,7 @@ describe('Unripe', function () { user.address, UNRIPE_BEAN, to6('1'), - to6('0.001') + to6('0.01') ) }) }) From 296d4471cc1c33e41da4c7d893254a60a8bab3d6 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Sat, 30 Mar 2024 21:31:12 +0200 Subject: [PATCH 21/86] Adjust first test values to new chop rate --- .../contracts/beanstalk/barn/UnripeFacet.sol | 14 ++++++++++++-- protocol/contracts/libraries/LibUnripe.sol | 1 + protocol/test/Unripe.test.js | 17 +++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index f00980acc6..61852b967c 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -164,6 +164,10 @@ contract UnripeFacet is ReentrancyGuard { */ function getPenalty(address unripeToken) external view returns (uint256 penalty) { // token // amount = 1e6 = 1 unripe token + console.log("///////////////// UnripeFacet.getPenalty() ///////////////////"); + console.log("Parameters"); + console.log("unripeToken: ", unripeToken); + console.log("amount: ", LibUnripe.DECIMALS); return getPenalizedUnderlying(unripeToken, LibUnripe.DECIMALS); } @@ -178,8 +182,13 @@ contract UnripeFacet is ReentrancyGuard { address unripeToken, uint256 amount ) public view returns (uint256 redeem) { - return - LibUnripe._getPenalizedUnderlying(unripeToken, amount, IBean(unripeToken).totalSupply()); + console.log("///////////////// UnripeFacet.getPenalizedUnderlying() ///////////////////"); + console.log("Parameters"); + console.log("unripeToken: ", unripeToken); + console.log("amount: ", amount); + console.log("totalSupply: ", IBean(unripeToken).totalSupply()); + // token // amount = 1e6 = 1 unripe token | supply = 1000 + return LibUnripe._getPenalizedUnderlying(unripeToken, amount, IBean(unripeToken).totalSupply()); } /** @@ -215,6 +224,7 @@ contract UnripeFacet is ReentrancyGuard { address unripeToken, address account ) external view returns (uint256 underlying) { + console.log("///////////////// UnripeFacet.balanceOfPenalizedUnderlying() ///////////////////"); return getPenalizedUnderlying(unripeToken, IERC20(unripeToken).balanceOf(account)); } diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index e9184ec202..fcbeffcae3 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -147,6 +147,7 @@ library LibUnripe { uint256 supply ) internal view returns (uint256 redeem) { require(isUnripe(unripeToken), "not vesting"); + // WE NO LONGER TAKE INTO ACCOUNT THE % AMOUNT REPAID BACK TO FERTILIZER // uint256 sharesBeingRedeemed = getRecapPaidPercentAmount(amount); uint256 sharesBeingRedeemed = amount; console.log("//////////////////// LibUnripe._getPenalizedUnderlying() ////////////////////"); diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index d5b51b821c..4e49b6aeed 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -81,6 +81,7 @@ describe('Unripe', function () { expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) // getUnderlying Returns the amount of Ripe Tokens that underly a given amount of Unripe Tokens. // Does NOT include the penalty associated with the percent of Sprouts that are Rinsable or Rinsed. + // NO CONNECTION WITH PENALTY PARAMS OR CHOP RATE expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal('0') @@ -95,7 +96,7 @@ describe('Unripe', function () { }) }) - describe('penalty go down', async function () { + describe.only('penalty go down', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, @@ -105,20 +106,28 @@ describe('Unripe', function () { }) it('getters', async function () { + // getUnderlyingPerUnripeToken Returns the amount of Ripe Tokens that underly a single Unripe Token. + // no connection with penalty params expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) + // GET PENALTY GETS CHOP PENALTY INFO FOR 1 SINGLE UNRIPE TOKEN + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.001')); + // GETPENDALIZEDUNDERLYING GETS CHOP PENALTY INFO FOR A GIVEN AMOUNT OF UNRIPE TOKENS + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.01')); expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('1')) + // balanceOfPenalizedUnderlying Returns the theoretical amount of the ripe asset in the account that underly a Farmer's balance of Unripe + // TODO: CONFIRM THIS + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) }) + ////////////////////////////////// START FROM HERE ////////////////////////////////// it('gets percents', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) + // TODO: THIS SHOULD CHANGE TO THE CONVERSION RATE (IE 0.01) FROM getPenalty() expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.001884')) }) From 5b9bfba72658decc32fb31b3095f71184c7d6be3 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Mon, 1 Apr 2024 20:07:30 +0300 Subject: [PATCH 22/86] adjust percentPenalty and rest of test values --- .../contracts/beanstalk/barn/UnripeFacet.sol | 22 ++++++- protocol/test/Unripe.test.js | 58 ++++++++++--------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index 61852b967c..21cc47abf8 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -248,7 +248,27 @@ contract UnripeFacet is ReentrancyGuard { * @return penalty The penalty % of Chopping. */ function getPercentPenalty(address unripeToken) external view returns (uint256 penalty) { - return LibUnripe.getRecapPaidPercentAmount(getRecapFundedPercent(unripeToken)); + console.log("///////////////// UnripeFacet.getPercentPenalty() ///////////////////"); + console.log("Parameters"); + console.log("unripeToken: ", unripeToken); + console.log("getRecapFundedPercent: ", getRecapFundedPercent(unripeToken)); + console.log("OLD RETURN VALUE getRecapPaidPercentAmount: ", LibUnripe.getRecapPaidPercentAmount(getRecapFundedPercent(unripeToken))); + // --> denominates to getRecapPaidPercentAmount() (5%) of the corresponding ripe tokens for an unripe token + // so if for unripe bean 10% of beans are recapitalized (getRecapFundedPercent) and 5% of those are paid to fertilizer + // (getRecapPaidPercentAmount) the final penalty % is 0.5% + + // THE PERCENT PENALTY NOW IS JUST THE %RECAPPED^2 of a given unripe token + // now it should be + // LibUnripe._getPenalizedUnderlying(unripeToken, 1e6, IBean(unripeToken).totalSupply()); + // or + console.log("Parameters"); + console.log("unripeToken: ", unripeToken); + console.log("amount: ", 1e6); + console.log("totalSupply: ", IBean(unripeToken).totalSupply()); + console.log("s.u[unripeToken].balanceOfUnderlying: ", s.u[unripeToken].balanceOfUnderlying); + console.log("% PENALTY NEW RETURN VALUE"); + // ADDED DECIMALS TO AVOID UNDERFLOW + return (s.u[unripeToken].balanceOfUnderlying ** 2).mul(LibUnripe.DECIMALS).div(IERC20(unripeToken).totalSupply() ** 2); } /** diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 4e49b6aeed..23bf69df7a 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -23,7 +23,6 @@ describe('Unripe', function () { this.token = await ethers.getContractAt('TokenFacet', this.diamond.address) this.bean = await ethers.getContractAt('MockToken', BEAN) await this.bean.connect(owner).approve(this.diamond.address, to6('100000000')) - this.unripeBean = await ethers.getContractAt('MockToken', UNRIPE_BEAN) this.unripeLP = await ethers.getContractAt('MockToken', UNRIPE_LP) await this.unripeLP.mint(userAddress, to6('1000')) @@ -32,6 +31,7 @@ describe('Unripe', function () { await this.unripeBean.connect(user).approve(this.diamond.address, to6('100000000')) await this.fertilizer.setFertilizerE(true, to6('10000')) await this.unripe.addUnripeToken(UNRIPE_BEAN, BEAN, ZERO_BYTES) + await this.unripe.addUnripeToken(UNRIPE_LP, BEAN, ZERO_BYTES) await this.bean.mint(ownerAddress, to6('100')) await this.season.siloSunrise(0) @@ -74,9 +74,9 @@ describe('Unripe', function () { // getUnderlyingPerUnripeToken Returns the amount of Ripe Tokens that underly a single Unripe Token. // no connection with penalty params expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - // no penalty - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0')) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal('0') + // getPenalty calls _getPenalizedUnderlying that returns calculates new chop rate + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.01')); expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) // getUnderlying Returns the amount of Ripe Tokens that underly a given amount of Unripe Tokens. @@ -84,19 +84,22 @@ describe('Unripe', function () { // NO CONNECTION WITH PENALTY PARAMS OR CHOP RATE expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal('0') + // balance of penalized underlying also calls _getPenalizedUnderlying that calculates new chop rate + // but with an amount equal to the unripe balance of the user that has 1000 unripe tokens so with a chop rate of + // 0.01 the balance of penalized underlying should be 1000 * 0.01 = 10 + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) }) it('gets percents', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal('0') expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0')) + // Same holds for Unripe LP with the same underlying balance and penalty params + expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) }) }) - describe.only('penalty go down', async function () { + describe('penalty go down', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, @@ -118,18 +121,15 @@ describe('Unripe', function () { expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) // balanceOfPenalizedUnderlying Returns the theoretical amount of the ripe asset in the account that underly a Farmer's balance of Unripe - // TODO: CONFIRM THIS expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) }) - ////////////////////////////////// START FROM HERE ////////////////////////////////// it('gets percents', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - // TODO: THIS SHOULD CHANGE TO THE CONVERSION RATE (IE 0.01) FROM getPenalty() - expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.001884')) + // Same holds for Unripe LP with the same underlying balance and penalty params + expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) }) }) @@ -148,20 +148,22 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.999')) + // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.001')) - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.100099')) - expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.999')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('0.99999')) + // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) + expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) }) it('changes balaces', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('999')) expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.01')) expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.999')) + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.99')) }) it('emits an event', async function () { @@ -194,20 +196,22 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.001')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.999')) + // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.001')) - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.100099')) - expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.999')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('0.99999')) + // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) + expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) }) it('changes balaces', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('999')) expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.01')) expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.999')) + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.99')) }) it('emits an event', async function () { From b00f2c7d6ecf35006a9d79ee67c2a966403aea0b Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 4 Apr 2024 19:37:22 +0300 Subject: [PATCH 23/86] clean up chop rate changes --- .../contracts/beanstalk/barn/UnripeFacet.sol | 40 +------------------ protocol/contracts/libraries/LibUnripe.sol | 22 +++------- protocol/test/Unripe.test.js | 22 ---------- 3 files changed, 7 insertions(+), 77 deletions(-) diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index 21cc47abf8..c316af2914 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -19,7 +19,6 @@ import {ReentrancyGuard} from "contracts/beanstalk/ReentrancyGuard.sol"; import {LibLockedUnderlying} from "contracts/libraries/LibLockedUnderlying.sol"; import {LibChop} from "contracts/libraries/LibChop.sol"; import {LibBarnRaise} from "contracts/libraries/LibBarnRaise.sol"; -import "hardhat/console.sol"; /** * @title UnripeFacet @@ -83,10 +82,6 @@ contract UnripeFacet is ReentrancyGuard { LibTransfer.From fromMode, LibTransfer.To toMode ) external payable nonReentrant returns (uint256) { - console.log("///////////////// UnripeFacet.chop() ///////////////////"); - console.log("Parameters"); - console.log("unripeToken: ", unripeToken); - console.log("amount: ", amount); // burn the token from the msg.sender address uint256 supply = IBean(unripeToken).totalSupply(); amount = LibTransfer.burnToken(IBean(unripeToken), amount, msg.sender, fromMode); @@ -101,7 +96,6 @@ contract UnripeFacet is ReentrancyGuard { IERC20(underlyingToken).sendToken(underlyingAmount, msg.sender, toMode); // emit the event emit Chop(msg.sender, unripeToken, amount, underlyingAmount); - console.log("\n\n/////////////////////////////////////// END CHOP ///////////////////////////////////////"); return underlyingAmount; } @@ -163,11 +157,6 @@ contract UnripeFacet is ReentrancyGuard { * @return penalty The current penalty for converting unripe --> ripe */ function getPenalty(address unripeToken) external view returns (uint256 penalty) { - // token // amount = 1e6 = 1 unripe token - console.log("///////////////// UnripeFacet.getPenalty() ///////////////////"); - console.log("Parameters"); - console.log("unripeToken: ", unripeToken); - console.log("amount: ", LibUnripe.DECIMALS); return getPenalizedUnderlying(unripeToken, LibUnripe.DECIMALS); } @@ -182,12 +171,6 @@ contract UnripeFacet is ReentrancyGuard { address unripeToken, uint256 amount ) public view returns (uint256 redeem) { - console.log("///////////////// UnripeFacet.getPenalizedUnderlying() ///////////////////"); - console.log("Parameters"); - console.log("unripeToken: ", unripeToken); - console.log("amount: ", amount); - console.log("totalSupply: ", IBean(unripeToken).totalSupply()); - // token // amount = 1e6 = 1 unripe token | supply = 1000 return LibUnripe._getPenalizedUnderlying(unripeToken, amount, IBean(unripeToken).totalSupply()); } @@ -224,7 +207,6 @@ contract UnripeFacet is ReentrancyGuard { address unripeToken, address account ) external view returns (uint256 underlying) { - console.log("///////////////// UnripeFacet.balanceOfPenalizedUnderlying() ///////////////////"); return getPenalizedUnderlying(unripeToken, IERC20(unripeToken).balanceOf(account)); } @@ -245,29 +227,9 @@ contract UnripeFacet is ReentrancyGuard { /** * @notice Returns the % penalty of Chopping an Unripe Token into its Ripe Token. * @param unripeToken The address of the Unripe Token. - * @return penalty The penalty % of Chopping. + * @return penalty The penalty % of Chopping derived from %Recapitalized^2. */ function getPercentPenalty(address unripeToken) external view returns (uint256 penalty) { - console.log("///////////////// UnripeFacet.getPercentPenalty() ///////////////////"); - console.log("Parameters"); - console.log("unripeToken: ", unripeToken); - console.log("getRecapFundedPercent: ", getRecapFundedPercent(unripeToken)); - console.log("OLD RETURN VALUE getRecapPaidPercentAmount: ", LibUnripe.getRecapPaidPercentAmount(getRecapFundedPercent(unripeToken))); - // --> denominates to getRecapPaidPercentAmount() (5%) of the corresponding ripe tokens for an unripe token - // so if for unripe bean 10% of beans are recapitalized (getRecapFundedPercent) and 5% of those are paid to fertilizer - // (getRecapPaidPercentAmount) the final penalty % is 0.5% - - // THE PERCENT PENALTY NOW IS JUST THE %RECAPPED^2 of a given unripe token - // now it should be - // LibUnripe._getPenalizedUnderlying(unripeToken, 1e6, IBean(unripeToken).totalSupply()); - // or - console.log("Parameters"); - console.log("unripeToken: ", unripeToken); - console.log("amount: ", 1e6); - console.log("totalSupply: ", IBean(unripeToken).totalSupply()); - console.log("s.u[unripeToken].balanceOfUnderlying: ", s.u[unripeToken].balanceOfUnderlying); - console.log("% PENALTY NEW RETURN VALUE"); - // ADDED DECIMALS TO AVOID UNDERFLOW return (s.u[unripeToken].balanceOfUnderlying ** 2).mul(LibUnripe.DECIMALS).div(IERC20(unripeToken).totalSupply() ** 2); } diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index fcbeffcae3..9e4df852c2 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -12,7 +12,6 @@ import {LibWell} from "./Well/LibWell.sol"; import {Call, IWell} from "contracts/interfaces/basin/IWell.sol"; import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; -import "hardhat/console.sol"; /** * @title LibUnripe @@ -141,19 +140,18 @@ library LibUnripe { emit SwitchUnderlyingToken(unripeToken, newUnderlyingToken); } + /** + * @notice Calculates the the penalized amount of Ripe Tokens corresponding to + * the amount of Unripe Tokens that are Chopped according to the current Chop Rate. + * The new chop rate is %Recapitalized^2. + */ function _getPenalizedUnderlying( address unripeToken, uint256 amount, uint256 supply ) internal view returns (uint256 redeem) { require(isUnripe(unripeToken), "not vesting"); - // WE NO LONGER TAKE INTO ACCOUNT THE % AMOUNT REPAID BACK TO FERTILIZER - // uint256 sharesBeingRedeemed = getRecapPaidPercentAmount(amount); - uint256 sharesBeingRedeemed = amount; - console.log("//////////////////// LibUnripe._getPenalizedUnderlying() ////////////////////"); - console.log("NEW sharesBeingRedeemed: ", sharesBeingRedeemed); - console.log("THIS SHOULD BE THE SAME AS THE AMOUNT PASSED IN"); - redeem = _getUnderlying(unripeToken, sharesBeingRedeemed, supply); + redeem = _getUnderlying(unripeToken, amount, supply); } /** @@ -241,14 +239,6 @@ library LibUnripe { uint256 supply ) internal view returns (uint256 redeem) { AppStorage storage s = LibAppStorage.diamondStorage(); - //* @param balanceOfUnderlying The number of Tokens underlying the Unripe Tokens (redemption pool). - // redeem = amount * ( balanceOfUnderlying / supply ) ^ 2 - console.log("//////////////////// LibUnripe._getUnderlying() ////////////////////"); - console.log("unripeToken: ", unripeToken); - console.log("unripe shares redeemed: ", amount); - console.log("unripe supply: ", supply); - console.log("unripe balanceOfUnderlying: ", s.u[unripeToken].balanceOfUnderlying); redeem = (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2); - console.log("FINAL REDEEM OF RIPE FROM UNRIPE AFTER SQUARING: ", redeem); } } diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 23bf69df7a..51db1f5c7f 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -71,22 +71,13 @@ describe('Unripe', function () { }) it('getters', async function () { - // getUnderlyingPerUnripeToken Returns the amount of Ripe Tokens that underly a single Unripe Token. - // no connection with penalty params expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - // getPenalty calls _getPenalizedUnderlying that returns calculates new chop rate expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.01')); expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - // getUnderlying Returns the amount of Ripe Tokens that underly a given amount of Unripe Tokens. - // Does NOT include the penalty associated with the percent of Sprouts that are Rinsable or Rinsed. - // NO CONNECTION WITH PENALTY PARAMS OR CHOP RATE expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - // balance of penalized underlying also calls _getPenalizedUnderlying that calculates new chop rate - // but with an amount equal to the unripe balance of the user that has 1000 unripe tokens so with a chop rate of - // 0.01 the balance of penalized underlying should be 1000 * 0.01 = 10 expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) }) @@ -94,7 +85,6 @@ describe('Unripe', function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal('0') expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - // Same holds for Unripe LP with the same underlying balance and penalty params expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) }) }) @@ -109,18 +99,13 @@ describe('Unripe', function () { }) it('getters', async function () { - // getUnderlyingPerUnripeToken Returns the amount of Ripe Tokens that underly a single Unripe Token. - // no connection with penalty params expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - // GET PENALTY GETS CHOP PENALTY INFO FOR 1 SINGLE UNRIPE TOKEN expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - // GETPENDALIZEDUNDERLYING GETS CHOP PENALTY INFO FOR A GIVEN AMOUNT OF UNRIPE TOKENS expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.01')); expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - // balanceOfPenalizedUnderlying Returns the theoretical amount of the ripe asset in the account that underly a Farmer's balance of Unripe expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) }) @@ -128,13 +113,10 @@ describe('Unripe', function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - // Same holds for Unripe LP with the same underlying balance and penalty params expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) }) }) - ///////////////////////// CHOP ///////////////////////// - describe('chop', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( @@ -148,11 +130,9 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) @@ -196,11 +176,9 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - // an unripe token is removed from circulation(supply) along with a reduction in the underlying ripe balance so this should change expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) From a1b36835857d7017d9587601f1b326683fa8f1f1 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 4 Apr 2024 23:39:16 +0300 Subject: [PATCH 24/86] fix broken chop convert tests --- protocol/test/ConvertUnripe.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/protocol/test/ConvertUnripe.test.js b/protocol/test/ConvertUnripe.test.js index f2e1c9a62d..b6d0e827a1 100644 --- a/protocol/test/ConvertUnripe.test.js +++ b/protocol/test/ConvertUnripe.test.js @@ -586,13 +586,13 @@ describe('Unripe Convert', function () { // CHECK TO SEE THAT RECAP AND PENALTY VALUES ARE UPDATED AFTER THE CONVERT it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('101000') - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.00101')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('999.90')) + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100909') + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010182')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('999.0')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) // same fert , less supply --> penalty goes down - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.00101')) - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1010')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010182')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.100909')) }) // TOTALS @@ -600,7 +600,7 @@ describe('Unripe Convert', function () { // UNRIPE BEAN DEPOSIT TEST expect(await this.siloGetters.getTotalDeposited(this.unripeBean.address)).to.eq(to6('100')); // RIPE BEAN CONVERTED TEST - expect(await this.siloGetters.getTotalDeposited(this.bean.address)).to.eq(to6('0.1')); + expect(await this.siloGetters.getTotalDeposited(this.bean.address)).to.eq(to6('1')); // TOTAL STALK TEST // 0.004 * 3 seasons = 0.012 expect(await this.siloGetters.totalStalk()).to.eq(toStalk('20.012')); @@ -621,7 +621,7 @@ describe('Unripe Convert', function () { // USER DEPOSITS TEST it('properly updates user deposits', async function () { expect((await this.siloGetters.getDeposit(userAddress, this.unripeBean.address, 0))[0]).to.eq(to6('100')); - expect((await this.siloGetters.getDeposit(userAddress, this.bean.address, 0))[0]).to.eq(to6('0.1')); + expect((await this.siloGetters.getDeposit(userAddress, this.bean.address, 0))[0]).to.eq(to6('1')); }); // EVENTS TEST @@ -629,9 +629,9 @@ describe('Unripe Convert', function () { await expect(this.result).to.emit(this.silo, 'RemoveDeposits') .withArgs(userAddress, this.unripeBean.address, [0], [to6('100')], to6('100'), [to6('10')]); await expect(this.result).to.emit(this.silo, 'AddDeposit') - .withArgs(userAddress, this.bean.address, 0 , to6('0.1'), to6('10')); + .withArgs(userAddress, this.bean.address, 0 , to6('1'), to6('10')); await expect(this.result).to.emit(this.convert, 'Convert') - .withArgs(userAddress, this.unripeBean.address, this.bean.address, to6('100') , to6('0.1')); + .withArgs(userAddress, this.unripeBean.address, this.bean.address, to6('100') , to6('1')); }); }); }); From af1cf159235dd971f2f857d6b4e110e8d98c5be5 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Tue, 9 Apr 2024 08:46:27 +0300 Subject: [PATCH 25/86] =?UTF-8?q?allow=20only=20single=20deposit=20updatin?= =?UTF-8?q?g=20for=20anti=20=CE=BB=20converts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- protocol/contracts/beanstalk/silo/ConvertFacet.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/contracts/beanstalk/silo/ConvertFacet.sol b/protocol/contracts/beanstalk/silo/ConvertFacet.sol index 50c86e41de..e8bc3f3aa9 100644 --- a/protocol/contracts/beanstalk/silo/ConvertFacet.sol +++ b/protocol/contracts/beanstalk/silo/ConvertFacet.sol @@ -85,6 +85,8 @@ contract ConvertFacet is ReentrancyGuard { { convertParams memory cp; (cp.toToken, cp.fromToken, toAmount, fromAmount, cp.account, cp.decreaseBDV) = LibConvert.convert(convertData); + + if (cp.decreaseBDV) {require(stems.length == 1 && amounts.length == 1, "Convert: DecreaseBDV only supports updating one deposit.");} require(fromAmount > 0, "Convert: From amount is 0."); From 605c9dba8dacf1fb12df81de21fc4583e7d56c10 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Tue, 9 Apr 2024 09:08:57 +0300 Subject: [PATCH 26/86] =?UTF-8?q?Add=20multiple=20deposit=20revert=20test?= =?UTF-8?q?=20for=20anti=20=CE=BB=20convert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- protocol/test/Convert.test.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/protocol/test/Convert.test.js b/protocol/test/Convert.test.js index 4e5089def8..a7f49d9ab1 100644 --- a/protocol/test/Convert.test.js +++ b/protocol/test/Convert.test.js @@ -490,7 +490,7 @@ describe('Convert', function () { await this.season.siloSunrise(0); await this.season.siloSunrise(0); - // simulate deposit bdv decrease for user2 by changing bdv selector to mockBdvIncrease ie 1.1e6 + // simulate deposit bdv increase for user2 by changing bdv selector to mockBdvIncrease ie 1.1e6 await this.silo.mockChangeBDVSelector(this.newSiloToken.address, this.silo.interface.getSighash("newMockBDVIncrease()")) currentBdv = await this.silo.newMockBDVIncrease() let depositResult = await this.siloGetters.getDeposit(userAddress, this.newSiloToken.address, this.stem) @@ -533,6 +533,32 @@ describe('Convert', function () { await expect(this.result).to.emit(this.silo, 'AddDeposit').withArgs(userAddress, this.newSiloToken.address, this.newStem, '100', '1100000'); // last param = updated bdv await expect(this.result).to.emit(this.convert, 'Convert').withArgs(userAddress, this.newSiloToken.address, this.newSiloToken.address, '100', '100'); }) + }) + + describe("anti lambda convert revert on multiple deposit update", async function () { + it("Reverts on multiple deposit input", async function () { + // ----------------------- SETUP ------------------------ + // user deposits 100 new silo token at stem 0 so 1000000 bdv + await this.newSiloToken.mint(userAddress, '10000000'); + await this.newSiloToken.connect(user).approve(this.silo.address, '1000000000'); + await this.silo.connect(user).deposit(this.newSiloToken.address, '100', EXTERNAL); + this.stem = await this.siloGetters.stemTipForToken(this.newSiloToken.address); + + // end germination: + await this.season.siloSunrise(0); + await this.season.siloSunrise(0); + + // ----------------------- CONVERT ------------------------ + await expect(this.convert.connect(user2).convert( + // CALLDATA + // amount, token ,account + ConvertEncoder.convertAntiLambdaToLambda('100', this.newSiloToken.address , userAddress), + // STEMS [] + [this.stem, this.stem], + // AMOUNTS [] + ['100', '100'] + )).to.be.revertedWith("Convert: DecreaseBDV only supports updating one deposit.") + }) }) }); From 1690e9828ed567b2ecbd1c021969ffb441eb7f4b Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 17:59:30 +0300 Subject: [PATCH 27/86] Remoce _getUnderlying and _getTotalPenalizedUnderlying and move logic to _getPenalizedUnderlying --- protocol/contracts/libraries/LibUnripe.sol | 27 ++-------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 9e4df852c2..76c7af2aa9 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -151,19 +151,8 @@ library LibUnripe { uint256 supply ) internal view returns (uint256 redeem) { require(isUnripe(unripeToken), "not vesting"); - redeem = _getUnderlying(unripeToken, amount, supply); - } - - /** - * @notice Calculates the the amount of Ripe Tokens that would be paid out if - * all Unripe Tokens were Chopped at the current Chop Rate. - */ - function _getTotalPenalizedUnderlying( - address unripeToken - ) internal view returns (uint256 redeem) { - require(isUnripe(unripeToken), "not vesting"); - uint256 supply = IERC20(unripeToken).totalSupply(); - redeem = _getUnderlying(unripeToken, getRecapPaidPercentAmount(supply), supply); + AppStorage storage s = LibAppStorage.diamondStorage(); + redeem = (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2); } /** @@ -229,16 +218,4 @@ library LibUnripe { AppStorage storage s = LibAppStorage.diamondStorage(); unripe = s.u[unripeToken].underlyingToken != address(0); } - - /** - * @notice Returns the underlying token amount of the unripe token. - */ - function _getUnderlying( - address unripeToken, - uint256 amount, - uint256 supply - ) internal view returns (uint256 redeem) { - AppStorage storage s = LibAppStorage.diamondStorage(); - redeem = (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2); - } } From 9c882f9f2038fdef374bf50b14cf6821e5798b29 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 18:04:34 +0300 Subject: [PATCH 28/86] remove _ from LibUnripe.getPenalizedUnderlying --- protocol/contracts/beanstalk/barn/UnripeFacet.sol | 2 +- protocol/contracts/libraries/Convert/LibChopConvert.sol | 2 +- protocol/contracts/libraries/LibChop.sol | 2 +- protocol/contracts/libraries/LibUnripe.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index c316af2914..8a26c938d8 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -171,7 +171,7 @@ contract UnripeFacet is ReentrancyGuard { address unripeToken, uint256 amount ) public view returns (uint256 redeem) { - return LibUnripe._getPenalizedUnderlying(unripeToken, amount, IBean(unripeToken).totalSupply()); + return LibUnripe.getPenalizedUnderlying(unripeToken, amount, IBean(unripeToken).totalSupply()); } /** diff --git a/protocol/contracts/libraries/Convert/LibChopConvert.sol b/protocol/contracts/libraries/Convert/LibChopConvert.sol index 3f807bb74b..c89dc425d0 100644 --- a/protocol/contracts/libraries/Convert/LibChopConvert.sol +++ b/protocol/contracts/libraries/Convert/LibChopConvert.sol @@ -52,7 +52,7 @@ library LibChopConvert { */ function getConvertedUnderlyingOut(address tokenIn, uint256 amountIn) internal view returns(uint256 amount) { // tokenIn == unripe bean address - amount = LibUnripe._getPenalizedUnderlying( + amount = LibUnripe.getPenalizedUnderlying( tokenIn, amountIn, IBean(tokenIn).totalSupply() diff --git a/protocol/contracts/libraries/LibChop.sol b/protocol/contracts/libraries/LibChop.sol index a56bacc55f..12c521809e 100644 --- a/protocol/contracts/libraries/LibChop.sol +++ b/protocol/contracts/libraries/LibChop.sol @@ -30,7 +30,7 @@ library LibChop { uint256 supply ) internal returns (address underlyingToken, uint256 underlyingAmount) { AppStorage storage s = LibAppStorage.diamondStorage(); - underlyingAmount = LibUnripe._getPenalizedUnderlying(unripeToken, amount, supply); + underlyingAmount = LibUnripe.getPenalizedUnderlying(unripeToken, amount, supply); LibUnripe.decrementUnderlying(unripeToken, underlyingAmount); underlyingToken = s.u[unripeToken].underlyingToken; } diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 76c7af2aa9..96e40d13ce 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -145,7 +145,7 @@ library LibUnripe { * the amount of Unripe Tokens that are Chopped according to the current Chop Rate. * The new chop rate is %Recapitalized^2. */ - function _getPenalizedUnderlying( + function getPenalizedUnderlying( address unripeToken, uint256 amount, uint256 supply From faf8806a3c5ab232ec122407a38019b73be9d574 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 18:14:26 +0300 Subject: [PATCH 29/86] Fix natspec in getDeltaBInfo --- protocol/contracts/libraries/Minting/LibWellMinting.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index 13e6adda72..dc77dfb5b2 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -150,10 +150,8 @@ library LibWellMinting { } /** - * @dev Calculates the deltaB for a given Well address - * and returns the deltaB, snapshot, reserves and ratios. - * @param well The address of the Well. - * @return deltaB The instantaneous delta B balance since the last `capture` call. + * @dev Calculates the delta B of a given well for a given set of well state parameters. + * Designed to work for instantaneous and twa delta B calculations. */ function getDeltaBInfo(address well, uint[] memory reserves, bytes memory snapshot, uint256 lookback ) internal view returns (int256, bytes memory, uint256[] memory, uint256[] memory) { @@ -233,7 +231,7 @@ library LibWellMinting { AppStorage storage s = LibAppStorage.diamondStorage(); Call[] memory pumps = IWell(well).pumps(); // well address , data[] - uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, C.BYTES_ZERO); + uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); // well, reserves, snapshot, lookback (int256 deltaB, , ,) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); return (deltaB); From fe889193c8354f57f1fdb4efa666a1dd579f2001 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 18:16:28 +0300 Subject: [PATCH 30/86] remove unused appstorage var in LibWellMinting --- protocol/contracts/libraries/Minting/LibWellMinting.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index dc77dfb5b2..ccbea3b188 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -227,8 +227,6 @@ library LibWellMinting { * @return deltaB The instantaneous delta B balance since the last `capture` call. */ function instantaneousDeltaB(address well) internal view returns (int256) { - - AppStorage storage s = LibAppStorage.diamondStorage(); Call[] memory pumps = IWell(well).pumps(); // well address , data[] uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); From 7a4ee6458b930ae8894ee0cb6d1da67b26846308 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 18:19:45 +0300 Subject: [PATCH 31/86] fix tab spacing on comment in LibWellMinting --- protocol/contracts/libraries/Minting/LibWellMinting.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index ccbea3b188..d7571fc643 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -228,9 +228,8 @@ library LibWellMinting { */ function instantaneousDeltaB(address well) internal view returns (int256) { Call[] memory pumps = IWell(well).pumps(); - // well address , data[] uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); - // well, reserves, snapshot, lookback + // well, reserves, snapshot, lookback (int256 deltaB, , ,) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); return (deltaB); } From e8f74f0b52d65369bc0d47a4dee4b2a621c5bb0c Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 18:42:27 +0300 Subject: [PATCH 32/86] add try catch in inst reserves caclulation --- .../contracts/libraries/Minting/LibWellMinting.sol | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index d7571fc643..c91175033d 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -228,10 +228,15 @@ library LibWellMinting { */ function instantaneousDeltaB(address well) internal view returns (int256) { Call[] memory pumps = IWell(well).pumps(); - uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); - // well, reserves, snapshot, lookback - (int256 deltaB, , ,) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); - return (deltaB); + try IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well,pumps[0].data) + returns (uint[] memory instReserves) { + uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); + // well, reserves, snapshot, lookback + (int256 deltaB, , ,) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); + return (deltaB); + } catch { + return 0; + } } } From 6f02dd2019cec27a2f721b425e5ec1b1ad46edba Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 18:48:15 +0300 Subject: [PATCH 33/86] Use LibUnripe.getPenalizedUnderlying in getPercentPenalty --- protocol/contracts/beanstalk/barn/UnripeFacet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index 8a26c938d8..af9cbe753b 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -230,7 +230,7 @@ contract UnripeFacet is ReentrancyGuard { * @return penalty The penalty % of Chopping derived from %Recapitalized^2. */ function getPercentPenalty(address unripeToken) external view returns (uint256 penalty) { - return (s.u[unripeToken].balanceOfUnderlying ** 2).mul(LibUnripe.DECIMALS).div(IERC20(unripeToken).totalSupply() ** 2); + return LibUnripe.getPenalizedUnderlying(unripeToken, LibUnripe.DECIMALS, IERC20(unripeToken).totalSupply()); } /** From ede9928502ec0c17b44073058ba26e30d8b3fe59 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 11 Apr 2024 19:02:22 +0300 Subject: [PATCH 34/86] Add percent penalty for unripe lp tests --- protocol/test/Unripe.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 51db1f5c7f..b0c83f6f1d 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -86,6 +86,7 @@ describe('Unripe', function () { expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0')) }) }) @@ -114,6 +115,7 @@ describe('Unripe', function () { expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0')) }) }) From 8e4f75fd705cca164a5b16981361c13bf528012e Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 12 Apr 2024 15:29:28 +0300 Subject: [PATCH 35/86] Remove redundant oracle failure check in setSoil --- .../contracts/beanstalk/sun/SeasonFacet/Sun.sol | 4 +--- protocol/test/Sun.test.js | 14 +++++--------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index e4ab138c2c..d01cee2050 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -248,9 +248,7 @@ contract Sun is Oracle { } // Set new soil. - // If the inst delta b is 0 it means that the oracle failed so the twa delta b is used. - setSoil(instDeltaB == 0 ? uint256(-twaDeltaB) : Math.min(uint256(-twaDeltaB), uint256(-instDeltaB))); - + setSoil(Math.min(uint256(-twaDeltaB), uint256(-instDeltaB))); } /** diff --git a/protocol/test/Sun.test.js b/protocol/test/Sun.test.js index d121ace3b6..cb3670af39 100644 --- a/protocol/test/Sun.test.js +++ b/protocol/test/Sun.test.js @@ -157,20 +157,16 @@ describe('Sun', function () { to: this.diamond.address, value: 0 }) - + // If the twaDeltaB fails, we assume the instantaneous is also manipulated + // And since we havent changes the reserves, the instantaneous deltaB is 0 // twadeltaB, CASE ID this.result = await this.season.sunSunrise('-100000000', 8); - await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000'); - await expect(await this.field.totalSoil()).to.be.equal('100000000'); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '0'); + await expect(await this.field.totalSoil()).to.be.equal('0'); }) it("rewards more than type(uint128).max Soil below peg", async function () { - // Here, since we haven't changed the reserves the instantaneous deltaB is 0 - // And we maniuplate the twaDeltaB to be a number that is greater than type(uint128).max - // Because we consider that a value of 0 returned by the oracle to be a failure, we set the soil to be the twaDeltaB - // which is greater than type(uint128).max and therefore reverts - // twadeltaB, CASE ID - await expect(this.season.sunSunrise('-340282366920938463463374607431768211456', '8')).to.be.revertedWith('SafeCast: value doesn\'t fit in 128 bits'); + await expect(this.season.setSoilE('340282366920938463463374607431768211456')).to.be.revertedWith('SafeCast: value doesn\'t fit in 128 bits'); }) it("delta B == 1", async function () { From 5307a8eaf2cf855bc938aa379aeb2ce90a6cf031 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 12 Apr 2024 15:35:52 +0300 Subject: [PATCH 36/86] rename getDeltaBInfo to getDeltaBInfoFromWell --- protocol/contracts/libraries/Minting/LibWellMinting.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index c91175033d..4bc2209f8f 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -153,7 +153,7 @@ library LibWellMinting { * @dev Calculates the delta B of a given well for a given set of well state parameters. * Designed to work for instantaneous and twa delta B calculations. */ - function getDeltaBInfo(address well, uint[] memory reserves, bytes memory snapshot, uint256 lookback + function getDeltaBInfoFromWell(address well, uint[] memory reserves, bytes memory snapshot, uint256 lookback ) internal view returns (int256, bytes memory, uint256[] memory, uint256[] memory) { // get well tokens @@ -213,7 +213,7 @@ library LibWellMinting { pumps[0].data ) returns (uint[] memory twaReserves, bytes memory snapshot) { // well, reserves, snapshot lookback - return (getDeltaBInfo(well, twaReserves, snapshot, block.timestamp.sub(s.season.timestamp))); + return (getDeltaBInfoFromWell(well, twaReserves, snapshot, block.timestamp.sub(s.season.timestamp))); } catch { // if the pump fails, return all 0s to avoid the sunrise reverting. @@ -231,8 +231,8 @@ library LibWellMinting { try IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well,pumps[0].data) returns (uint[] memory instReserves) { uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); - // well, reserves, snapshot, lookback - (int256 deltaB, , ,) = getDeltaBInfo(well, instReserves, new bytes(0) , 0); + // well, reserves, snapshot, lookback + (int256 deltaB, , ,) = getDeltaBInfoFromWell(well, instReserves, new bytes(0) , 0); return (deltaB); } catch { return 0; From a6283448ae7f503af3974e85c5ccf651d63f6f04 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 12 Apr 2024 15:51:14 +0300 Subject: [PATCH 37/86] Refactor Ibeanstalk interface --- .../tokens/Fertilizer/Fertilizer.sol | 20 +++++++------------ .../tokens/Fertilizer/FertilizerImage.sol | 7 +------ .../tokens/Fertilizer/Internalizer.sol | 9 +++++++++ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/protocol/contracts/tokens/Fertilizer/Fertilizer.sol b/protocol/contracts/tokens/Fertilizer/Fertilizer.sol index 6f844b9139..07e74a5032 100644 --- a/protocol/contracts/tokens/Fertilizer/Fertilizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Fertilizer.sol @@ -4,19 +4,13 @@ pragma solidity ^0.7.6; pragma experimental ABIEncoderV2; import "./Internalizer.sol"; +import {IBeanstalk} from "./Internalizer.sol"; /** * @author publius * @title Barn Raiser */ -interface IBS { - function payFertilizer(address account, uint256 amount) external; - function beansPerFertilizer() external view returns (uint128); - function getEndBpf() external view returns (uint128); - function remainingRecapitalization() external view returns (uint256); -} - // Inherits Internalizer thus inherits ERC1155Upgradeable and the uri function // The end Fert Facet only gets the interface of this contract contract Fertilizer is Internalizer { @@ -79,7 +73,7 @@ contract Fertilizer is Internalizer { uint256[] memory, // amounts bytes memory // data ) internal virtual override { - uint256 bpf = uint256(IBS(owner()).beansPerFertilizer()); + uint256 bpf = uint256(IBeanstalk(owner()).beansPerFertilizer()); if (from != address(0)) _update(from, ids, bpf); _update(to, ids, bpf); } @@ -97,7 +91,7 @@ contract Fertilizer is Internalizer { uint256 bpf ) internal { uint256 amount = __update(account, ids, bpf); - if (amount > 0) IBS(owner()).payFertilizer(account, amount); + if (amount > 0) IBeanstalk(owner()).payFertilizer(account, amount); } /** @@ -132,7 +126,7 @@ contract Fertilizer is Internalizer { * @return beans - the amount of fertilized beans the fertilizer owner has */ function balanceOfFertilized(address account, uint256[] memory ids) external view returns (uint256 beans) { - uint256 bpf = uint256(IBS(owner()).beansPerFertilizer()); + uint256 bpf = uint256(IBeanstalk(owner()).beansPerFertilizer()); for (uint256 i; i < ids.length; ++i) { uint256 stopBpf = bpf < ids[i] ? bpf : ids[i]; uint256 deltaBpf = stopBpf - _balances[ids[i]][account].lastBpf; @@ -148,7 +142,7 @@ contract Fertilizer is Internalizer { * @return beans - the amount of unfertilized beans the fertilizer owner has */ function balanceOfUnfertilized(address account, uint256[] memory ids) external view returns (uint256 beans) { - uint256 bpf = uint256(IBS(owner()).beansPerFertilizer()); + uint256 bpf = uint256(IBeanstalk(owner()).beansPerFertilizer()); for (uint256 i; i < ids.length; ++i) { if (ids[i] > bpf) beans = beans.add(ids[i].sub(bpf).mul(_balances[ids[i]][account].amount)); } @@ -158,13 +152,13 @@ contract Fertilizer is Internalizer { @notice Returns the value remaining to recapitalize beanstalk */ function remaining() public view returns (uint256) { - return IBS(owner()).remainingRecapitalization(); + return IBeanstalk(owner()).remainingRecapitalization(); } /** @notice Returns the id a fertilizer will receive when minted */ function getMintId() public view returns (uint256) { - return uint256(IBS(owner()).getEndBpf()); + return uint256(IBeanstalk(owner()).getEndBpf()); } } \ No newline at end of file diff --git a/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol b/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol index 7a5dbe276e..dc8d681669 100644 --- a/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol +++ b/protocol/contracts/tokens/Fertilizer/FertilizerImage.sol @@ -5,18 +5,13 @@ pragma experimental ABIEncoderV2; import {LibStrings} from "contracts/libraries/LibStrings.sol"; import {LibBytes64} from "contracts/libraries/LibBytes64.sol"; +import {IBeanstalk} from "./Internalizer.sol"; /** * @title FertilizerImage * @author deadmanwalking */ -// interface to interact with the Beanstalk contract -interface IBeanstalk { - function beansPerFertilizer() external view returns (uint128); - function getEndBpf() external view returns (uint128); - function getFertilizer(uint128) external view returns (uint256); -} contract FertilizerImage { address internal constant BEANSTALK = 0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5; diff --git a/protocol/contracts/tokens/Fertilizer/Internalizer.sol b/protocol/contracts/tokens/Fertilizer/Internalizer.sol index ac31adf1ae..5611971bd9 100644 --- a/protocol/contracts/tokens/Fertilizer/Internalizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Internalizer.sol @@ -20,6 +20,15 @@ import {LibBytes64} from "contracts/libraries/LibBytes64.sol"; * @title Fertilizer before the Unpause */ +// interface to interact with the Beanstalk contract +interface IBeanstalk { + function payFertilizer(address account, uint256 amount) external; + function beansPerFertilizer() external view returns (uint128); + function getEndBpf() external view returns (uint128); + function remainingRecapitalization() external view returns (uint256); + function getFertilizer(uint128) external view returns (uint256); +} + contract Internalizer is OwnableUpgradeable, ReentrancyGuardUpgradeable, Fertilizer1155, FertilizerImage { using SafeERC20Upgradeable for IERC20; From c2c3b9590064438449456809105e72a05798859f Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 12 Apr 2024 16:08:53 +0300 Subject: [PATCH 38/86] Add chop unripe lp test --- protocol/test/Unripe.test.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index b0c83f6f1d..94444ece8b 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -32,7 +32,7 @@ describe('Unripe', function () { await this.fertilizer.setFertilizerE(true, to6('10000')) await this.unripe.addUnripeToken(UNRIPE_BEAN, BEAN, ZERO_BYTES) await this.unripe.addUnripeToken(UNRIPE_LP, BEAN, ZERO_BYTES) - await this.bean.mint(ownerAddress, to6('100')) + await this.bean.mint(ownerAddress, to6('200')) await this.season.siloSunrise(0) }) @@ -67,6 +67,10 @@ describe('Unripe', function () { UNRIPE_BEAN, to6('100') ) + await this.unripe.connect(owner).addUnderlying( + UNRIPE_LP, + to6('100') + ) await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('0')) }) @@ -86,16 +90,20 @@ describe('Unripe', function () { expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.01')) }) }) - describe('penalty go down', async function () { + describe('change penalty params but penalty affected only by recapitalization ', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, to6('100') ) + await this.unripe.connect(owner).addUnderlying( + UNRIPE_LP, + to6('100') + ) await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('100')) }) @@ -115,7 +123,7 @@ describe('Unripe', function () { expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.01')) }) }) From 7ec19c212504546f1c8bb5e432a2542a87d128af Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 12 Apr 2024 16:59:47 +0300 Subject: [PATCH 39/86] clean up and add maths in convertUnripe test --- protocol/test/ConvertUnripe.test.js | 34 ++++++++++------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/protocol/test/ConvertUnripe.test.js b/protocol/test/ConvertUnripe.test.js index b6d0e827a1..2be450b677 100644 --- a/protocol/test/ConvertUnripe.test.js +++ b/protocol/test/ConvertUnripe.test.js @@ -567,64 +567,54 @@ describe('Unripe Convert', function () { describe('basic urBEAN-->BEAN convert', function () { - // PERFORM A DEPOSIT AND A CONVERT BEFORE EVERY TEST beforeEach(async function () { - - // user deposits 200 UrBEAN to the silo from external account await this.silo.connect(user).deposit(this.unripeBean.address, to6('200'), EXTERNAL); - // GO FORWARD 3 SEASONs AND DONT DISTRIBUTE ANY REWARDS TO SILO - // season 11 await this.season.siloSunrise(0); await this.season.siloSunrise(0); await this.season.siloSunrise(0); - // SET FERT PARAMS await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('100')) - // INTERACTING WITH THE CONVERT FACET CONVERT(bytes calldata convertData, int96[] memory stems,uint256[] memory amounts) FUNCTION + // bytes calldata convertData, int96[] memory stems, uint256[] memory amounts) this.result = await this.convert.connect(user).convert(ConvertEncoder.convertUnripeToRipe(to6('100') , this.unripeBean.address) , ['0'], [to6('100')] ); + }); - // CHECK TO SEE THAT RECAP AND PENALTY VALUES ARE UPDATED AFTER THE CONVERT - it('getters', async function () { + it.only('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100909') + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.100909')) + // convert happens, 100e6 removed from unripe supply, 1e6 removed from underlying ripe + // new params: supply = 9900,000000 , s.u[unripeToken].balanceOfUnderlying = 999,000000 + // penalty after convert ---> (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2) = 999000000 ^ 2 * 1e6 / 9900000000 ^ 2 = 0.010182 expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010182')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('999.0')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) // same fert , less supply --> penalty goes down expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010182')) + // getUnderlying = s.u[unripeToken].balanceOfUnderlying.mul(amount).div(supply) + // = 999000000 * 1000000 / 9900000000 = 100909 expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.100909')) }) - // TOTALS it('properly updates total values', async function () { - // UNRIPE BEAN DEPOSIT TEST expect(await this.siloGetters.getTotalDeposited(this.unripeBean.address)).to.eq(to6('100')); - // RIPE BEAN CONVERTED TEST expect(await this.siloGetters.getTotalDeposited(this.bean.address)).to.eq(to6('1')); - // TOTAL STALK TEST - // 0.004 * 3 seasons = 0.012 + // 0.004 * 3 seasons passed = 0.012 stalk expect(await this.siloGetters.totalStalk()).to.eq(toStalk('20.012')); - // VERIFY urBEANS ARE BURNED expect(await this.unripeBean.totalSupply()).to.be.equal(to6('9900')) }); - // USER VALUES TEST it('properly updates user values', async function () { - // USER STALK TEST - // 1 urBEAN yields 2/10000 grown stalk every season witch is claimable with mow() + // 1 urBEAN yields 2/10000 grown stalk every season, claimable with mow() // after every silo interaction(here --> convert). // Since we go forward 3 seasons after the deposit, the user should now have 1200/10000 grown stalk - // not affected by the unripe --> ripe convert + // unaffected by the unripe --> ripe convert expect(await this.siloGetters.balanceOfStalk(userAddress)).to.eq(toStalk('20.012')); }); - // USER DEPOSITS TEST it('properly updates user deposits', async function () { expect((await this.siloGetters.getDeposit(userAddress, this.unripeBean.address, 0))[0]).to.eq(to6('100')); expect((await this.siloGetters.getDeposit(userAddress, this.bean.address, 0))[0]).to.eq(to6('1')); }); - // EVENTS TEST it('emits events', async function () { await expect(this.result).to.emit(this.silo, 'RemoveDeposits') .withArgs(userAddress, this.unripeBean.address, [0], [to6('100')], to6('100'), [to6('10')]); From 027119442ab8ee76b7ea72783ec73d43a2d7751a Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Sat, 13 Apr 2024 14:56:26 +0300 Subject: [PATCH 40/86] Refactor LibConvert to use convertParams struct and adjust convertFacet as needed --- .../contracts/beanstalk/silo/ConvertFacet.sol | 29 +++-- .../libraries/Convert/LibConvert.sol | 102 +++++++++--------- .../mocks/mockFacets/MockConvertFacet.sol | 10 +- protocol/test/ConvertUnripe.test.js | 2 +- 4 files changed, 73 insertions(+), 70 deletions(-) diff --git a/protocol/contracts/beanstalk/silo/ConvertFacet.sol b/protocol/contracts/beanstalk/silo/ConvertFacet.sol index e8bc3f3aa9..588ab3fcc1 100644 --- a/protocol/contracts/beanstalk/silo/ConvertFacet.sol +++ b/protocol/contracts/beanstalk/silo/ConvertFacet.sol @@ -26,14 +26,6 @@ contract ConvertFacet is ReentrancyGuard { using SafeCast for uint256; using LibSafeMath32 for uint32; - struct convertParams { - address toToken; - address fromToken; - uint256 grownStalk; - address account; - bool decreaseBDV; - } - event Convert( address indexed account, address fromToken, @@ -83,12 +75,12 @@ contract ConvertFacet is ReentrancyGuard { nonReentrant returns (int96 toStem, uint256 fromAmount, uint256 toAmount, uint256 fromBdv, uint256 toBdv) { - convertParams memory cp; - (cp.toToken, cp.fromToken, toAmount, fromAmount, cp.account, cp.decreaseBDV) = LibConvert.convert(convertData); + uint256 grownStalk; + LibConvert.convertParams memory cp = LibConvert.convert(convertData); if (cp.decreaseBDV) {require(stems.length == 1 && amounts.length == 1, "Convert: DecreaseBDV only supports updating one deposit.");} - require(fromAmount > 0, "Convert: From amount is 0."); + require(cp.fromAmount > 0, "Convert: From amount is 0."); // Replace account with msg.sender if no account is specified. if(cp.account == address(0)) cp.account = msg.sender; @@ -99,22 +91,27 @@ contract ConvertFacet is ReentrancyGuard { if (cp.fromToken != cp.toToken) LibSilo._mow(cp.account, cp.toToken); // Withdraw the tokens from the deposit. - (cp.grownStalk, fromBdv) = _withdrawTokens( + (grownStalk, fromBdv) = _withdrawTokens( cp.fromToken, stems, amounts, - fromAmount, + cp.fromAmount, cp.account ); // Calculate the bdv of the new deposit. - uint256 newBdv = LibTokenSilo.beanDenominatedValue(cp.toToken, toAmount); + uint256 newBdv = LibTokenSilo.beanDenominatedValue(cp.toToken, cp.toAmount); // If `decreaseBDV` flag is not enabled, set toBDV to the max of the two bdvs. toBdv = (newBdv > fromBdv || cp.decreaseBDV) ? newBdv : fromBdv; - toStem = _depositTokensForConvert(cp.toToken, toAmount, toBdv, cp.grownStalk, cp.account); - emit Convert(cp.account, cp.fromToken, cp.toToken, fromAmount, toAmount); + toStem = _depositTokensForConvert(cp.toToken, cp.toAmount, toBdv, grownStalk, cp.account); + + // Retrieve the rest of return parameters from the convert struct. + toAmount = cp.toAmount; + fromAmount = cp.fromAmount; + + emit Convert(cp.account, cp.fromToken, cp.toToken, cp.fromAmount, cp.toAmount); } /** diff --git a/protocol/contracts/libraries/Convert/LibConvert.sol b/protocol/contracts/libraries/Convert/LibConvert.sol index 010ce70b83..fde3844564 100644 --- a/protocol/contracts/libraries/Convert/LibConvert.sol +++ b/protocol/contracts/libraries/Convert/LibConvert.sol @@ -23,6 +23,15 @@ library LibConvert { using LibConvertData for bytes; using LibWell for address; + struct convertParams { + address toToken; + address fromToken; + uint256 fromAmount; + uint256 toAmount; + address account; + bool decreaseBDV; + } + /** * @notice Takes in bytes object that has convert input data encoded into it for a particular convert for * a specified pool and returns the in and out convert amounts and token addresses and bdv @@ -34,141 +43,134 @@ library LibConvert { */ function convert(bytes calldata convertData) external - returns ( - address tokenOut, - address tokenIn, - uint256 amountOut, - uint256 amountIn, - address account, - bool decreaseBDV - ) + returns (convertParams memory cp) { LibConvertData.ConvertKind kind = convertData.convertKind(); // if (kind == LibConvertData.ConvertKind.BEANS_TO_CURVE_LP) { - // (tokenOut, tokenIn, amountOut, amountIn) = LibCurveConvert + // (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibCurveConvert // .convertBeansToLP(convertData); if (kind == LibConvertData.ConvertKind.BEANS_TO_WELL_LP) { - (tokenOut, tokenIn, amountOut, amountIn) = LibWellConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibWellConvert .convertBeansToLP(convertData); } else if (kind == LibConvertData.ConvertKind.WELL_LP_TO_BEANS) { - (tokenOut, tokenIn, amountOut, amountIn) = LibWellConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibWellConvert .convertLPToBeans(convertData); } else if (kind == LibConvertData.ConvertKind.UNRIPE_BEANS_TO_UNRIPE_LP) { - (tokenOut, tokenIn, amountOut, amountIn) = LibUnripeConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibUnripeConvert .convertBeansToLP(convertData); } else if (kind == LibConvertData.ConvertKind.UNRIPE_LP_TO_UNRIPE_BEANS) { - (tokenOut, tokenIn, amountOut, amountIn) = LibUnripeConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibUnripeConvert .convertLPToBeans(convertData); } else if (kind == LibConvertData.ConvertKind.UNRIPE_TO_RIPE) { - (tokenOut, tokenIn, amountOut, amountIn) = LibChopConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibChopConvert .convertUnripeToRipe(convertData); } else if (kind == LibConvertData.ConvertKind.LAMBDA_LAMBDA) { - (tokenOut, tokenIn, amountOut, amountIn) = LibLambdaConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibLambdaConvert .convert(convertData); } else if (kind == LibConvertData.ConvertKind.ANTI_LAMBDA_LAMBDA) { - (tokenOut, tokenIn, amountOut, amountIn, account, decreaseBDV) = LibLambdaConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount, cp.account, cp.decreaseBDV) = LibLambdaConvert .antiConvert(convertData); } else if (kind == LibConvertData.ConvertKind.CURVE_LP_TO_BEANS) { - (tokenOut, tokenIn, amountOut, amountIn) = LibCurveConvert + (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibCurveConvert .convertLPToBeans(convertData); } else { revert("Convert: Invalid payload"); } } - function getMaxAmountIn(address tokenIn, address tokenOut) + function getMaxAmountIn(address fromToken, address toToken) internal view returns (uint256) { /// BEAN:3CRV LP -> BEAN - if (tokenIn == C.CURVE_BEAN_METAPOOL && tokenOut == C.BEAN) + if (fromToken == C.CURVE_BEAN_METAPOOL && toToken == C.BEAN) return LibCurveConvert.lpToPeg(C.CURVE_BEAN_METAPOOL); /// BEAN -> BEAN:3CRV LP // NOTE: cannot convert due to bean:3crv dewhitelisting - // if (tokenIn == C.BEAN && tokenOut == C.CURVE_BEAN_METAPOOL) + // if (fromToken == C.BEAN && toToken == C.CURVE_BEAN_METAPOOL) // return LibCurveConvert.beansToPeg(C.CURVE_BEAN_METAPOOL); // Lambda -> Lambda & // Anti-Lambda -> Lambda - if (tokenIn == tokenOut) + if (fromToken == toToken) return type(uint256).max; // Bean -> Well LP Token - if (tokenIn == C.BEAN && tokenOut.isWell()) - return LibWellConvert.beansToPeg(tokenOut); + if (fromToken == C.BEAN && toToken.isWell()) + return LibWellConvert.beansToPeg(toToken); // Well LP Token -> Bean - if (tokenIn.isWell() && tokenOut == C.BEAN) - return LibWellConvert.lpToPeg(tokenIn); + if (fromToken.isWell() && toToken == C.BEAN) + return LibWellConvert.lpToPeg(fromToken); // urLP Convert - if (tokenIn == C.UNRIPE_LP){ + if (fromToken == C.UNRIPE_LP){ // UrBEANETH -> urBEAN - if (tokenOut == C.UNRIPE_BEAN) + if (toToken == C.UNRIPE_BEAN) return LibUnripeConvert.lpToPeg(); // UrBEANETH -> BEANETH - if (tokenOut == LibBarnRaise.getBarnRaiseWell()) + if (toToken == LibBarnRaise.getBarnRaiseWell()) return type(uint256).max; } // urBEAN Convert - if (tokenIn == C.UNRIPE_BEAN){ + if (fromToken == C.UNRIPE_BEAN){ // urBEAN -> urLP - if (tokenOut == C.UNRIPE_LP) + if (toToken == C.UNRIPE_LP) return LibUnripeConvert.beansToPeg(); // UrBEAN -> BEAN - if (tokenOut == C.BEAN) + if (toToken == C.BEAN) return type(uint256).max; } revert("Convert: Tokens not supported"); } - function getAmountOut(address tokenIn, address tokenOut, uint256 amountIn) + function getAmountOut(address fromToken, address toToken, uint256 fromAmount) internal view returns (uint256) { /// BEAN:3CRV LP -> BEAN - if (tokenIn == C.CURVE_BEAN_METAPOOL && tokenOut == C.BEAN) - return LibCurveConvert.getBeanAmountOut(C.CURVE_BEAN_METAPOOL, amountIn); + if (fromToken == C.CURVE_BEAN_METAPOOL && toToken == C.BEAN) + return LibCurveConvert.getBeanAmountOut(C.CURVE_BEAN_METAPOOL, fromAmount); /// BEAN -> BEAN:3CRV LP // NOTE: cannot convert due to bean:3crv dewhitelisting - // if (tokenIn == C.BEAN && tokenOut == C.CURVE_BEAN_METAPOOL) - // return LibCurveConvert.getLPAmountOut(C.CURVE_BEAN_METAPOOL, amountIn); + // if (fromToken == C.BEAN && toToken == C.CURVE_BEAN_METAPOOL) + // return LibCurveConvert.getLPAmountOut(C.CURVE_BEAN_METAPOOL, fromAmount); /// urLP -> urBEAN - if (tokenIn == C.UNRIPE_LP && tokenOut == C.UNRIPE_BEAN) - return LibUnripeConvert.getBeanAmountOut(amountIn); + if (fromToken == C.UNRIPE_LP && toToken == C.UNRIPE_BEAN) + return LibUnripeConvert.getBeanAmountOut(fromAmount); /// urBEAN -> urLP - if (tokenIn == C.UNRIPE_BEAN && tokenOut == C.UNRIPE_LP) - return LibUnripeConvert.getLPAmountOut(amountIn); + if (fromToken == C.UNRIPE_BEAN && toToken == C.UNRIPE_LP) + return LibUnripeConvert.getLPAmountOut(fromAmount); // Lambda -> Lambda & // Anti-Lambda -> Lambda - if (tokenIn == tokenOut) - return amountIn; + if (fromToken == toToken) + return fromAmount; // Bean -> Well LP Token - if (tokenIn == C.BEAN && tokenOut.isWell()) - return LibWellConvert.getLPAmountOut(tokenOut, amountIn); + if (fromToken == C.BEAN && toToken.isWell()) + return LibWellConvert.getLPAmountOut(toToken, fromAmount); // Well LP Token -> Bean - if (tokenIn.isWell() && tokenOut == C.BEAN) - return LibWellConvert.getBeanAmountOut(tokenIn, amountIn); + if (fromToken.isWell() && toToken == C.BEAN) + return LibWellConvert.getBeanAmountOut(fromToken, fromAmount); // UrBEAN -> Bean - if (tokenIn == C.UNRIPE_BEAN && tokenOut == C.BEAN) - return LibChopConvert.getConvertedUnderlyingOut(tokenIn, amountIn); + if (fromToken == C.UNRIPE_BEAN && toToken == C.BEAN) + return LibChopConvert.getConvertedUnderlyingOut(fromToken, fromAmount); // UrBEANETH -> BEANETH - if (tokenIn == C.UNRIPE_LP && tokenOut == LibBarnRaise.getBarnRaiseWell()) - return LibChopConvert.getConvertedUnderlyingOut(tokenIn, amountIn); + if (fromToken == C.UNRIPE_LP && toToken == LibBarnRaise.getBarnRaiseWell()) + return LibChopConvert.getConvertedUnderlyingOut(fromToken, fromAmount); revert("Convert: Tokens not supported"); } diff --git a/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol b/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol index 1fb08b3bcd..64e4725217 100644 --- a/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockConvertFacet.sol @@ -60,9 +60,13 @@ contract MockConvertFacet is ConvertFacet { bool decreaseBDV ) { IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - (toToken, fromToken, toAmount, fromAmount , account , decreaseBDV) = LibConvert.convert( - convertData - ); + LibConvert.convertParams memory cp = LibConvert.convert(convertData); + toToken = cp.toToken; + fromToken = cp.fromToken; + toAmount = cp.toAmount; + fromAmount = cp.fromAmount; + account = cp.account; + decreaseBDV = cp.decreaseBDV; IERC20(toToken).safeTransfer(msg.sender, toAmount); } } diff --git a/protocol/test/ConvertUnripe.test.js b/protocol/test/ConvertUnripe.test.js index 2be450b677..32c0694b2b 100644 --- a/protocol/test/ConvertUnripe.test.js +++ b/protocol/test/ConvertUnripe.test.js @@ -578,7 +578,7 @@ describe('Unripe Convert', function () { }); - it.only('getters', async function () { + it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.100909')) // convert happens, 100e6 removed from unripe supply, 1e6 removed from underlying ripe From 092a4a4188fafd1bafd4ba92c38c4d641afb813f Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Sat, 13 Apr 2024 15:00:01 +0300 Subject: [PATCH 41/86] remove duplicate declaration in instantaneousDeltaB --- protocol/contracts/libraries/Minting/LibWellMinting.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/protocol/contracts/libraries/Minting/LibWellMinting.sol b/protocol/contracts/libraries/Minting/LibWellMinting.sol index 4bc2209f8f..d448310fe5 100644 --- a/protocol/contracts/libraries/Minting/LibWellMinting.sol +++ b/protocol/contracts/libraries/Minting/LibWellMinting.sol @@ -230,7 +230,6 @@ library LibWellMinting { Call[] memory pumps = IWell(well).pumps(); try IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well,pumps[0].data) returns (uint[] memory instReserves) { - uint[] memory instReserves = IInstantaneousPump(pumps[0].target).readInstantaneousReserves(well, pumps[0].data); // well, reserves, snapshot, lookback (int256 deltaB, , ,) = getDeltaBInfoFromWell(well, instReserves, new bytes(0) , 0); return (deltaB); From 2e2c21c28bc202db85dd21ea4a247db00d58fe41 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 17 Apr 2024 16:10:28 +0300 Subject: [PATCH 42/86] fix chop rate formula and add first attempted test --- .../contracts/libraries/LibFertilizer.sol | 16 ++++- protocol/contracts/libraries/LibUnripe.sol | 15 +++- protocol/test/Unripe.test.js | 71 ++++++++++++------- 3 files changed, 74 insertions(+), 28 deletions(-) diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 9067a26d0f..47734a94ef 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -18,6 +18,7 @@ import {LibDiamond} from "contracts/libraries/LibDiamond.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import {LibWell} from "contracts/libraries/Well/LibWell.sol"; import {LibUsdOracle} from "contracts/libraries/Oracle/LibUsdOracle.sol"; +import "hardhat/console.sol"; /** * @author Publius @@ -219,13 +220,22 @@ library LibFertilizer { returns (uint256 remaining) { AppStorage storage s = LibAppStorage.diamondStorage(); - uint256 totalDollars = C + uint256 totalDollars = getTotalRecapDollarsNeeded(); + if (s.recapitalized >= totalDollars) return 0; + return totalDollars.sub(s.recapitalized); + } + + /** + * @dev Returns the total dollar amount needed to recapitalize Beanstalk. + * @return totalDollars The total dollar amount. + */ + function getTotalRecapDollarsNeeded() internal view returns(uint256) { + uint256 totalDollars = C .dollarPerUnripeLP() .mul(C.unripeLP().totalSupply()) .div(DECIMALS); totalDollars = totalDollars / 1e6 * 1e6; // round down to nearest USDC - if (s.recapitalized >= totalDollars) return 0; - return totalDollars.sub(s.recapitalized); + return totalDollars; } /** diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 96e40d13ce..9df5ee8d25 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -12,6 +12,8 @@ import {LibWell} from "./Well/LibWell.sol"; import {Call, IWell} from "contracts/interfaces/basin/IWell.sol"; import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; +import {LibFertilizer} from "./LibFertilizer.sol"; +import "hardhat/console.sol"; /** * @title LibUnripe @@ -152,7 +154,18 @@ library LibUnripe { ) internal view returns (uint256 redeem) { require(isUnripe(unripeToken), "not vesting"); AppStorage storage s = LibAppStorage.diamondStorage(); - redeem = (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2); + uint256 usdValueRaised = s.recapitalized; + uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); + uint256 totalRipeUnderlying = s.u[unripeToken].balanceOfUnderlying; + console.log("totalRipeUnderlying: ", totalRipeUnderlying); + console.log("usdValueRaised: ", usdValueRaised); + console.log("totalUsdNeeded: ", totalUsdNeeded); + console.log("amount: ", amount); + console.log("supply: ", supply); + // total redeemable * %DollarRecapitalized^2 * share of unripe tokens + // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; + // redeem = 25 * (25/50)^2 * 100/100 = 25 * 0.5^2 * 1 = 6.25 + redeem = totalRipeUnderlying.mul(usdValueRaised ** 2).div(totalUsdNeeded ** 2).mul(amount).div(supply); } /** diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 94444ece8b..4b63c35b6d 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -25,9 +25,9 @@ describe('Unripe', function () { await this.bean.connect(owner).approve(this.diamond.address, to6('100000000')) this.unripeBean = await ethers.getContractAt('MockToken', UNRIPE_BEAN) this.unripeLP = await ethers.getContractAt('MockToken', UNRIPE_LP) - await this.unripeLP.mint(userAddress, to6('1000')) + // await this.unripeLP.mint(userAddress, to6('1000')) await this.unripeLP.connect(user).approve(this.diamond.address, to6('100000000')) - await this.unripeBean.mint(userAddress, to6('1000')) + // await this.unripeBean.mint(userAddress, to6('1000')) await this.unripeBean.connect(user).approve(this.diamond.address, to6('100000000')) await this.fertilizer.setFertilizerE(true, to6('10000')) await this.unripe.addUnripeToken(UNRIPE_BEAN, BEAN, ZERO_BYTES) @@ -127,45 +127,68 @@ describe('Unripe', function () { }) }) - describe('chop', async function () { + ////////////////////////////////////////////////////////////// + // Example 2: balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100 + + // When all fertilizer is sold, balanceOfUnderlying is 50 tokens (totalusdneeded = 50) + // Total Supply of unripe is 100. Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. + // If 50% of Fertilizer is sold, balanceOfUnderlying should be 25. + // We want the user to redeem 50%^2 = 25% of their total amount. + // If the entire supply was chopped. They should get 12.5 tokens. + + describe.only('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { beforeEach(async function () { + // we need total dollars needed to be 50 * 1e6 + // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS + // 50 * 1e6 = 530618 * supply / 1e6 + // solve for supply --> we get 94229747,200434211 --> round to 94229747 / 1e6 = to6(94.229747) + await this.unripeLP.mint(userAddress, to6('95.229747')) + // unripe bean supply == 100 + await this.unripeBean.mint(userAddress, to6('100')) await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, - to6('100') + to6('25') // balanceOfUnderlying is 25 ) - await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('100')) - this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('1'), EXTERNAL, EXTERNAL) + //(50% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized + await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) + // user chops the whole unripe bean supply + this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) }) - it('getters', async function () { - expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) - expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) - expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) - }) + // it('getters', async function () { + // // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); + // expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + // // this divides by total supply so ---> "SafeMath: division by zero" when chop amount == total supply + // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') + // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) + // expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) + // expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) + // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) + // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) + // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) + // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) + // }) - it('changes balaces', async function () { - expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.01')) - expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.99')) + it.only('changes balaces', async function () { + // expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('900')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('12.5')); + // expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')); + // expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.99')) }) it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, UNRIPE_BEAN, - to6('1'), - to6('0.01') + to6('100'), + to6('12.5') ) }) }) + ////////////////////////////////////////// + + describe('chop', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( From c91d8db26b2bd6b88eea754ea8f456d5688e7862 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 17 Apr 2024 16:45:21 +0300 Subject: [PATCH 43/86] add example 3 test --- protocol/test/Unripe.test.js | 70 +++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 4b63c35b6d..8752a389b2 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -136,13 +136,13 @@ describe('Unripe', function () { // We want the user to redeem 50%^2 = 25% of their total amount. // If the entire supply was chopped. They should get 12.5 tokens. - describe.only('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { + describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { beforeEach(async function () { // we need total dollars needed to be 50 * 1e6 // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS // 50 * 1e6 = 530618 * supply / 1e6 - // solve for supply --> we get 94229747,200434211 --> round to 94229747 / 1e6 = to6(94.229747) - await this.unripeLP.mint(userAddress, to6('95.229747')) + // solve for supply --> we get 94229747,200434211 --> round to 94229747 / 1e6 = to6(94.229747) --> round to nearest usdc = 94.23 + await this.unripeLP.mint(userAddress, to6('95')) // unripe bean supply == 100 await this.unripeBean.mint(userAddress, to6('100')) await this.unripe.connect(owner).addUnderlying( @@ -169,7 +169,7 @@ describe('Unripe', function () { // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) // }) - it.only('changes balaces', async function () { + it('changes balaces', async function () { // expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('900')) expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('12.5')); // expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')); @@ -186,6 +186,68 @@ describe('Unripe', function () { }) }) + + // ### Example 3: balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100 + // When all fertilizer is sold, balanceOfUnderlying is 100 tokens. + // Total Supply of unripe is 200. Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. + // If 50% of Fertilizer is sold, balanceOfUnderlying should be 50. + // We want the user to redeem 50%^2 = 25% of their total amount. + // If the entire supply was chopped. They should get 12.5 tokens. + + describe.only('chop balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100', async function () { + beforeEach(async function () { + // we need total dollars needed to be 50 * 1e6 + // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS + // we need total dollars needed to be 100 * 1e6 + // solve for supply --> we get 188459494,4 --> round to nearest usdc = 189 + await this.unripeLP.mint(userAddress, to6('189')) + // unripe bean supply == 200 + await this.unripeBean.mint(userAddress, to6('200')) + await this.unripe.connect(owner).addUnderlying( + UNRIPE_BEAN, + to6('50') // balanceOfUnderlying is 50 + ) + // s.recapitalized=50, getTotalDollarsNeeded = 100 + // we want the user to redeem 50% = totalusdraised/totalusdneeded = 50/100 = 0.5 + //(50% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized + await this.fertilizer.connect(owner).setPenaltyParams(to6('50'), to6('100')) + // user chops the whole unripe bean supply + this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('200'), EXTERNAL, EXTERNAL) + }) + + it('getters', async function () { + // these divide by total supply so ---> "SafeMath: division by zero" when chop amount == total supply + // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) + // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.0625')) + // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.0625')) + // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.0625')) + // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('0')) + // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('0')) + + // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); + expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('37.5')) + expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) + }) + + it('changes balaces', async function () { + expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('0')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('12.5')); + expect(await this.unripeBean.totalSupply()).to.be.equal(to6('0')); + // 50 underlying at the start - 12.5 redeemed = 37.5 + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('37.5')) + }) + + it('emits an event', async function () { + await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( + user.address, + UNRIPE_BEAN, + to6('200'), + to6('12.5') + ) + }) + }) + ////////////////////////////////////////// From cbd29dd832a85e233550d5b92ffa4fe201f11c6d Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 18 Apr 2024 16:56:06 +0300 Subject: [PATCH 44/86] change to new formula according to total underlying and add tests --- protocol/contracts/libraries/LibUnripe.sol | 10 +- protocol/test/Unripe.test.js | 234 +++++++++++++++------ 2 files changed, 172 insertions(+), 72 deletions(-) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 9df5ee8d25..6ce9b5f81e 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -156,16 +156,18 @@ library LibUnripe { AppStorage storage s = LibAppStorage.diamondStorage(); uint256 usdValueRaised = s.recapitalized; uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); - uint256 totalRipeUnderlying = s.u[unripeToken].balanceOfUnderlying; - console.log("totalRipeUnderlying: ", totalRipeUnderlying); + uint256 currentRipeUnderlying = s.u[unripeToken].balanceOfUnderlying; + console.log("currentRipeUnderlying: ", currentRipeUnderlying); + console.log("totalRipeUnderlying: ", currentRipeUnderlying.mul(totalUsdNeeded).div(usdValueRaised)); console.log("usdValueRaised: ", usdValueRaised); console.log("totalUsdNeeded: ", totalUsdNeeded); console.log("amount: ", amount); console.log("supply: ", supply); // total redeemable * %DollarRecapitalized^2 * share of unripe tokens // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; - // redeem = 25 * (25/50)^2 * 100/100 = 25 * 0.5^2 * 1 = 6.25 - redeem = totalRipeUnderlying.mul(usdValueRaised ** 2).div(totalUsdNeeded ** 2).mul(amount).div(supply); + // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying + // So eventually redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply + redeem = currentRipeUnderlying.mul(usdValueRaised).div(totalUsdNeeded).mul(amount).div(supply); } /** diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 8752a389b2..f280231479 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -61,8 +61,16 @@ describe('Unripe', function () { expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal('0') }) - describe('deposit underlying', async function () { + + describe.only('deposit underlying', async function () { beforeEach(async function () { + // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS + // we need total dollars needed to be 100 * 1e6 + // solve for supply --> we get 188459494,4 --> round to nearest usdc = 189 + await this.unripeLP.mint(userAddress, to6('189')) + // total supply of unripe bean == 1000 + await this.unripeBean.mint(userAddress, to6('100')) + await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, to6('100') @@ -71,31 +79,41 @@ describe('Unripe', function () { UNRIPE_LP, to6('100') ) - await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('0')) + // s.recapitalized, s.feritilized + await this.fertilizer.connect(owner).setPenaltyParams(to6('50'), to6('0')) }) it('getters', async function () { - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.01')); + // 100 urBeans | 100 underlying beans --> 1 urBean per 1 underlying bean ratio + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('1')) + // getPenalty calls getPenalizedUnderlying with amount = 1 + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 100 * 50 / 100 * 1 / 100 = 0.5 + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.5')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.5')); expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('50')) }) it('gets percents', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal('0') - expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.01')) + // 100 underlying to 100 unripe bean --> 100% funded + expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('1')) + expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.498569')) + expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.5')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.264550')) }) }) - describe('change penalty params but penalty affected only by recapitalization ', async function () { + describe('change fertilizer penalty params', async function () { beforeEach(async function () { + // total supply of unripe lp == 1000 + await this.unripeLP.mint(userAddress, to6('1000')) + // total supply of unripe bean == 1000 + await this.unripeBean.mint(userAddress, to6('1000')) await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, to6('100') @@ -109,39 +127,46 @@ describe('Unripe', function () { it('getters', async function () { expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.003559')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.003559')); expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.01')); expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10')) + // more precision due to it being a larget number with 6 decimal precision + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('3.559985')) }) it('gets percents', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.01')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.01')) + expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.003559')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.003559')) }) }) - ////////////////////////////////////////////////////////////// + //////////////////////////////////// CHOPS ////////////////////////////////////////////// + + //////////////// THIS PASSES ////////////////////// // Example 2: balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100 // When all fertilizer is sold, balanceOfUnderlying is 50 tokens (totalusdneeded = 50) // Total Supply of unripe is 100. Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. // If 50% of Fertilizer is sold, balanceOfUnderlying should be 25. - // We want the user to redeem 50%^2 = 25% of their total amount. + // We want the user to redeem 50%^2 = 25% of their total amount // If the entire supply was chopped. They should get 12.5 tokens. + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 25 * 0.5 * 100/100 = 12.5 + describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { beforeEach(async function () { // we need total dollars needed to be 50 * 1e6 // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS // 50 * 1e6 = 530618 * supply / 1e6 - // solve for supply --> we get 94229747,200434211 --> round to 94229747 / 1e6 = to6(94.229747) --> round to nearest usdc = 94.23 + // solve for supply --> we get 94229747,2 --> round to nearest usdc = 95 await this.unripeLP.mint(userAddress, to6('95')) // unripe bean supply == 100 await this.unripeBean.mint(userAddress, to6('100')) @@ -149,31 +174,37 @@ describe('Unripe', function () { UNRIPE_BEAN, to6('25') // balanceOfUnderlying is 25 ) - //(50% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized + + // s.recapitalized=25, getTotalDollarsNeeded = 50 + //(50% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) // user chops the whole unripe bean supply this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) + + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 25 * 0.5 * 100/100 = 12.5 }) - // it('getters', async function () { - // // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); - // expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - // // this divides by total supply so ---> "SafeMath: division by zero" when chop amount == total supply - // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) - // expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) - // expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) - // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) - // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) - // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) - // }) + it('getters', async function () { + // this divides by total supply so ---> "SafeMath: division by zero" when chop amount == total supply + // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') + // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) + // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) + // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) + // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) + // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) + + // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); + expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('12.5')) + expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) + }) it('changes balaces', async function () { - // expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('900')) + expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('0')) expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('12.5')); - // expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')); - // expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.99')) + expect(await this.unripeBean.totalSupply()).to.be.equal(to6('0')); + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('12.5')) }) it('emits an event', async function () { @@ -186,17 +217,84 @@ describe('Unripe', function () { }) }) + //////////////// THIS PASSES ////////////////////// + // Still Example 2 + // When all fertilizer is sold, balanceOfUnderlying is 50 tokens. + // Total Supply of unripe is 100. + // Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. + // If 25% of Fertilizer is sold, balanceOfUnderlying should be 12.5. + // We want the user to redeem 25%^2 = 6.25% of their total amount + // If the entire supply was chopped, they should get 3.125 tokens. + + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 12.5 * 0.25 * 100/100 = 3.125 + + describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { + beforeEach(async function () { + // we need total dollars needed to be 50 * 1e6 + // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS + // 50 * 1e6 = 530618 * supply / 1e6 + // solve for supply --> we get 94229747,2 --> round to nearest usdc = 95 + await this.unripeLP.mint(userAddress, to6('95')) + // unripe bean supply == 100 + await this.unripeBean.mint(userAddress, to6('100')) + await this.unripe.connect(owner).addUnderlying( + UNRIPE_BEAN, + to6('12.5') // balanceOfUnderlying is 12.5 + ) + + // s.recapitalized=12.5, getTotalDollarsNeeded = 50 + //(25% of all Fertilizer is sold, balanceOfUnderlying=12.5.) s.recapitalized, s.fertilized + await this.fertilizer.connect(owner).setPenaltyParams(to6('12.5'), to6('100')) + // user chops the whole unripe bean supply + this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 12.5 * 0.25 * 100/100 = 3.125 + }) + + it('getters', async function () { + // this divides by total supply so ---> "SafeMath: division by zero" when chop amount == total supply + // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') + // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) + // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) + // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) + // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) + // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) + // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); + expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('9.375')) + expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) + }) + + it('changes balaces', async function () { + expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('0')); + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.125')); + expect(await this.unripeBean.totalSupply()).to.be.equal(to6('0')); + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('9.375')) + }) + + it('emits an event', async function () { + await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( + user.address, + UNRIPE_BEAN, + to6('100'), + to6('3.125') + ) + }) + }) // ### Example 3: balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100 // When all fertilizer is sold, balanceOfUnderlying is 100 tokens. // Total Supply of unripe is 200. Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. - // If 50% of Fertilizer is sold, balanceOfUnderlying should be 50. - // We want the user to redeem 50%^2 = 25% of their total amount. - // If the entire supply was chopped. They should get 12.5 tokens. + // If 25% of Fertilizer is sold, balanceOfUnderlying should be 25. + // We want the user to redeem 25%^2 = 6.25% of the underlying. + // If half the supply was chopped, they should get 3.125 tokens. + + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 25 * 0.25 * 100/200 = 3.125 - describe.only('chop balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100', async function () { + describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100', async function () { beforeEach(async function () { - // we need total dollars needed to be 50 * 1e6 // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS // we need total dollars needed to be 100 * 1e6 // solve for supply --> we get 188459494,4 --> round to nearest usdc = 189 @@ -205,52 +303,52 @@ describe('Unripe', function () { await this.unripeBean.mint(userAddress, to6('200')) await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, - to6('50') // balanceOfUnderlying is 50 + to6('25') // balanceOfUnderlying is 25 ) // s.recapitalized=50, getTotalDollarsNeeded = 100 - // we want the user to redeem 50% = totalusdraised/totalusdneeded = 50/100 = 0.5 - //(50% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized - await this.fertilizer.connect(owner).setPenaltyParams(to6('50'), to6('100')) - // user chops the whole unripe bean supply - this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('200'), EXTERNAL, EXTERNAL) + //(25% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized + await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) + // user chops half the unripe bean supply + this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 25 * 0.25 * 100/200 = 3.125 }) it('getters', async function () { - // these divide by total supply so ---> "SafeMath: division by zero" when chop amount == total supply - // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.0625')) - // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.0625')) - // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.0625')) - // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('0')) - // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('0')) - + // new rate for underlying per unripe token + // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 21.875 * 0.25 * 1/100 = 0.054687 + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.21875')) + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.054687')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.054687')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.21875')) + expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('21.875')) + // 100urBeans * 0.054687 = 5.46875 + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('5.46875')) // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('37.5')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('21.875')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) }) it('changes balaces', async function () { - expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('0')) - expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('12.5')); - expect(await this.unripeBean.totalSupply()).to.be.equal(to6('0')); - // 50 underlying at the start - 12.5 redeemed = 37.5 - expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('37.5')) + expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('100')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.125')); + expect(await this.unripeBean.totalSupply()).to.be.equal(to6('100')); + // 25 underlying at the start - 3.125 redeemed = 21.875 + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('21.875')) }) it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, UNRIPE_BEAN, - to6('200'), - to6('12.5') + to6('100'), + to6('3.125') ) }) }) - - ////////////////////////////////////////// - - + describe('chop', async function () { beforeEach(async function () { await this.unripe.connect(owner).addUnderlying( From fc44fb20e64c1c6cd647754d5b0bcd69b062aa2a Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 19 Apr 2024 11:02:18 +0300 Subject: [PATCH 45/86] final test fixes --- protocol/contracts/libraries/LibUnripe.sol | 7 -- protocol/test/Unripe.test.js | 96 +++++++++++++--------- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 6ce9b5f81e..83fccb05fd 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -13,7 +13,6 @@ import {Call, IWell} from "contracts/interfaces/basin/IWell.sol"; import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; import {LibFertilizer} from "./LibFertilizer.sol"; -import "hardhat/console.sol"; /** * @title LibUnripe @@ -157,12 +156,6 @@ library LibUnripe { uint256 usdValueRaised = s.recapitalized; uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); uint256 currentRipeUnderlying = s.u[unripeToken].balanceOfUnderlying; - console.log("currentRipeUnderlying: ", currentRipeUnderlying); - console.log("totalRipeUnderlying: ", currentRipeUnderlying.mul(totalUsdNeeded).div(usdValueRaised)); - console.log("usdValueRaised: ", usdValueRaised); - console.log("totalUsdNeeded: ", totalUsdNeeded); - console.log("amount: ", amount); - console.log("supply: ", supply); // total redeemable * %DollarRecapitalized^2 * share of unripe tokens // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index f280231479..b664948124 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -51,6 +51,8 @@ describe('Unripe', function () { }) it('getters', async function () { + await this.unripeBean.mint(userAddress, to6('1000')) + await this.unripeLP.mint(userAddress, to6('1000')) expect(await this.unripe.getRecapPaidPercent()).to.be.equal('0') expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('0') expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0')) @@ -62,13 +64,13 @@ describe('Unripe', function () { }) - describe.only('deposit underlying', async function () { + describe('deposit underlying', async function () { beforeEach(async function () { // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS // we need total dollars needed to be 100 * 1e6 // solve for supply --> we get 188459494,4 --> round to nearest usdc = 189 await this.unripeLP.mint(userAddress, to6('189')) - // total supply of unripe bean == 1000 + // total supply of unripe bean == 100 await this.unripeBean.mint(userAddress, to6('100')) await this.unripe.connect(owner).addUnderlying( @@ -108,12 +110,16 @@ describe('Unripe', function () { }) }) - describe('change fertilizer penalty params', async function () { + + describe('deposit underlying, change fertilizer penalty params and urBean supply', async function () { beforeEach(async function () { - // total supply of unripe lp == 1000 - await this.unripeLP.mint(userAddress, to6('1000')) + // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS + // we need total dollars needed to be 100 * 1e6 + // solve for supply --> we get 188459494,4 --> round to nearest usdc = 189 + await this.unripeLP.mint(userAddress, to6('189')) // total supply of unripe bean == 1000 await this.unripeBean.mint(userAddress, to6('1000')) + await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, to6('100') @@ -126,24 +132,23 @@ describe('Unripe', function () { }) it('getters', async function () { + // 1000 urBeans | 100 underlying beans --> 0.1 urBean per 1 underlying bean ratio expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.003559')) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.003559')); + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.1')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')); expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('100')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.1')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) - // more precision due to it being a larget number with 6 decimal precision - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('3.559985')) + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('100')) }) it('gets percents', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.188459')) - expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.003559')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.003559')) + expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.997138')) + expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.1')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.529100')) }) }) @@ -161,7 +166,7 @@ describe('Unripe', function () { // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; // redeem = 25 * 0.5 * 100/100 = 12.5 - describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { + describe('chop whole supply with balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100, fert 50% sold', async function () { beforeEach(async function () { // we need total dollars needed to be 50 * 1e6 // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS @@ -229,7 +234,7 @@ describe('Unripe', function () { // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; // redeem = 12.5 * 0.25 * 100/100 = 3.125 - describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100', async function () { + describe('chop whole supply with balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100, fert 25% sold', async function () { beforeEach(async function () { // we need total dollars needed to be 50 * 1e6 // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS @@ -293,7 +298,7 @@ describe('Unripe', function () { // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; // redeem = 25 * 0.25 * 100/200 = 3.125 - describe('chop balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100', async function () { + describe('chop half the supply balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { beforeEach(async function () { // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS // we need total dollars needed to be 100 * 1e6 @@ -305,8 +310,7 @@ describe('Unripe', function () { UNRIPE_BEAN, to6('25') // balanceOfUnderlying is 25 ) - // s.recapitalized=50, getTotalDollarsNeeded = 100 - //(25% of Fertilizer is sold, balanceOfUnderlying=25.) s.recapitalized, s.fertilized + // s.recapitalized=25 await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) // user chops half the unripe bean supply this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) @@ -323,9 +327,8 @@ describe('Unripe', function () { expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.054687')) expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.21875')) expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('21.875')) - // 100urBeans * 0.054687 = 5.46875 + // 100urBeans balance * 0.054687 = 5.46875 expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('5.46875')) - // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('21.875')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) @@ -349,48 +352,61 @@ describe('Unripe', function () { }) }) - describe('chop', async function () { + // Same as above but with different transfer modes used + describe('chop, different transfer modes, half the supply, balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { beforeEach(async function () { + // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS + // we need total dollars needed to be 100 * 1e6 + // solve for supply --> we get 188459494,4 --> round to nearest usdc = 189 + await this.unripeLP.mint(userAddress, to6('189')) + // unripe bean supply == 200 + await this.unripeBean.mint(userAddress, to6('200')) await this.unripe.connect(owner).addUnderlying( UNRIPE_BEAN, - to6('100') - ) - await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('100')) + to6('25') // balanceOfUnderlying is 25 + ) + // s.recapitalized=25 + await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) await this.token.connect(user).transferToken( UNRIPE_BEAN, user.address, - to6('1'), + to6('100'), EXTERNAL, INTERNAL - ) - this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('10'), INTERNAL_TOLERANT, EXTERNAL) + ) + this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), INTERNAL_TOLERANT, EXTERNAL) }) it('getters', async function () { + // new rate for underlying per unripe token + // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 21.875 * 0.25 * 1/100 = 0.054687 + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.21875')) + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.054687')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.054687')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.21875')) + expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('21.875')) + // 100urBeans balance * 0.054687 = 5.46875 + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('5.46875')) expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('99.99')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('21.875')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) - expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) }) it('changes balaces', async function () { - expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('0.01')) - expect(await this.unripeBean.totalSupply()).to.be.equal(to6('999')) - expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('99.99')) + expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('100')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.125')); + expect(await this.unripeBean.totalSupply()).to.be.equal(to6('100')); + // 25 underlying at the start - 3.125 redeemed = 21.875 + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('21.875')) }) it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, UNRIPE_BEAN, - to6('1'), - to6('0.01') + to6('100'), + to6('3.125') ) }) }) From 08980e455a76286503091f85ec07dd33e1c8209b Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Tue, 23 Apr 2024 16:19:21 +0300 Subject: [PATCH 46/86] FIx unripe chop tests --- protocol/test/ConvertUnripe.test.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/protocol/test/ConvertUnripe.test.js b/protocol/test/ConvertUnripe.test.js index 32c0694b2b..abbe0d3968 100644 --- a/protocol/test/ConvertUnripe.test.js +++ b/protocol/test/ConvertUnripe.test.js @@ -557,7 +557,7 @@ describe('Unripe Convert', function () { }); // Unripe to Ripe test - describe('convert unripe beans to beans', async function () { + describe.only('convert unripe beans to beans', async function () { beforeEach(async function () { // GO TO SEASON 10 @@ -575,28 +575,24 @@ describe('Unripe Convert', function () { await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('100')) // bytes calldata convertData, int96[] memory stems, uint256[] memory amounts) this.result = await this.convert.connect(user).convert(ConvertEncoder.convertUnripeToRipe(to6('100') , this.unripeBean.address) , ['0'], [to6('100')] ); - }); it('getters', async function () { expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.100909')) - // convert happens, 100e6 removed from unripe supply, 1e6 removed from underlying ripe - // new params: supply = 9900,000000 , s.u[unripeToken].balanceOfUnderlying = 999,000000 - // penalty after convert ---> (s.u[unripeToken].balanceOfUnderlying ** 2).mul(amount).div(supply ** 2) = 999000000 ^ 2 * 1e6 / 9900000000 ^ 2 = 0.010182 - expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010182')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('999.0')) + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal(to6('0.100949')) + // new params: supply = 9900,000000 , s.u[unripeToken].balanceOfUnderlying = 999.403698 + expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.006019')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('999.403698')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) // same fert , less supply --> penalty goes down - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010182')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.006019')) // getUnderlying = s.u[unripeToken].balanceOfUnderlying.mul(amount).div(supply) - // = 999000000 * 1000000 / 9900000000 = 100909 - expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.100909')) + expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.100949')) }) it('properly updates total values', async function () { expect(await this.siloGetters.getTotalDeposited(this.unripeBean.address)).to.eq(to6('100')); - expect(await this.siloGetters.getTotalDeposited(this.bean.address)).to.eq(to6('1')); + expect(await this.siloGetters.getTotalDeposited(this.bean.address)).to.eq(to6('0.596302')); // 0.004 * 3 seasons passed = 0.012 stalk expect(await this.siloGetters.totalStalk()).to.eq(toStalk('20.012')); expect(await this.unripeBean.totalSupply()).to.be.equal(to6('9900')) @@ -612,16 +608,18 @@ describe('Unripe Convert', function () { it('properly updates user deposits', async function () { expect((await this.siloGetters.getDeposit(userAddress, this.unripeBean.address, 0))[0]).to.eq(to6('100')); - expect((await this.siloGetters.getDeposit(userAddress, this.bean.address, 0))[0]).to.eq(to6('1')); + expect((await this.siloGetters.getDeposit(userAddress, this.bean.address, 0))[0]).to.eq(to6('0.596302')); }); it('emits events', async function () { await expect(this.result).to.emit(this.silo, 'RemoveDeposits') .withArgs(userAddress, this.unripeBean.address, [0], [to6('100')], to6('100'), [to6('10')]); await expect(this.result).to.emit(this.silo, 'AddDeposit') - .withArgs(userAddress, this.bean.address, 0 , to6('1'), to6('10')); + .withArgs(userAddress, this.bean.address, 0 , to6('0.596302'), to6('10')); + // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 1000 * (100/16770) * 100/10000 = 0.596302 await expect(this.result).to.emit(this.convert, 'Convert') - .withArgs(userAddress, this.unripeBean.address, this.bean.address, to6('100') , to6('1')); + .withArgs(userAddress, this.unripeBean.address, this.bean.address, to6('100') , to6('0.596302')); }); }); }); From 7972addeb5210c36341f032498e477930adc5d2a Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 25 Apr 2024 20:24:19 +0300 Subject: [PATCH 47/86] Add unripe lp chot test and fix recapitalization bug and urLP supply chop dollars needed bug --- .../contracts/libraries/LibFertilizer.sol | 14 ++++ protocol/contracts/libraries/LibUnripe.sol | 13 +++- .../mocks/mockFacets/MockUnripeFacet.sol | 4 ++ protocol/test/ConvertUnripe.test.js | 2 +- protocol/test/Unripe.test.js | 69 +++++++++++++++++++ 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 47734a94ef..93953491c7 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -238,6 +238,20 @@ library LibFertilizer { return totalDollars; } + /** + * @dev Returns the total dollar amount needed to recapitalize Beanstalk + * for the supply of Unripe LP. + * @return totalDollars The total dollar amount. + */ + function getTotalRecapDollarsNeeded(uint256 urLPsupply) internal view returns(uint256) { + uint256 totalDollars = C + .dollarPerUnripeLP() + .mul(urLPsupply) + .div(DECIMALS); + totalDollars = totalDollars / 1e6 * 1e6; // round down to nearest USDC + return totalDollars; + } + /** * @dev Removes the first fertilizer id in the queue. * fFirst is the lowest active Fertilizer Id (see AppStorage) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 83fccb05fd..85199b6ec7 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -13,7 +13,7 @@ import {Call, IWell} from "contracts/interfaces/basin/IWell.sol"; import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; import {LibFertilizer} from "./LibFertilizer.sol"; - +import "hardhat/console.sol"; /** * @title LibUnripe * @author Publius @@ -154,8 +154,17 @@ library LibUnripe { require(isUnripe(unripeToken), "not vesting"); AppStorage storage s = LibAppStorage.diamondStorage(); uint256 usdValueRaised = s.recapitalized; - uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); + // !!!!!!!!!!!!!! getTotalRecapDollarsNeeded() queries for the total urLP supply which is burned in UnripeFacet.sol + // TODO: Modify getTotalRecapDollarsNeeded() to accept the total LP supply as a parameter? + // note: Supply here is the total supply querited before the burn + uint256 totalUsdNeeded = unripeToken == C.UNRIPE_LP ? LibFertilizer.getTotalRecapDollarsNeeded(supply) : LibFertilizer.getTotalRecapDollarsNeeded(); uint256 currentRipeUnderlying = s.u[unripeToken].balanceOfUnderlying; + console.log("currentRipeUnderlying", currentRipeUnderlying); + console.log("usdValueRaised", usdValueRaised); + console.log("totalUsdNeeded", totalUsdNeeded); + console.log("amount", amount); + console.log("supply", supply); + console.log("s.recapitalized", s.recapitalized); // total redeemable * %DollarRecapitalized^2 * share of unripe tokens // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying diff --git a/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol b/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol index 5e3199fe3a..6f6e77f886 100644 --- a/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol @@ -20,6 +20,10 @@ contract MockUnripeFacet is UnripeFacet { s.u[unripeToken].merkleRoot = root; } + function getRecapitalized() external view returns (uint256) { + return s.recapitalized; + } + function addUnderlying(address unripeToken, uint256 amount) external payable diff --git a/protocol/test/ConvertUnripe.test.js b/protocol/test/ConvertUnripe.test.js index abbe0d3968..33ad360be5 100644 --- a/protocol/test/ConvertUnripe.test.js +++ b/protocol/test/ConvertUnripe.test.js @@ -557,7 +557,7 @@ describe('Unripe Convert', function () { }); // Unripe to Ripe test - describe.only('convert unripe beans to beans', async function () { + describe('convert unripe beans to beans', async function () { beforeEach(async function () { // GO TO SEASON 10 diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index b664948124..960853ad33 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -351,6 +351,75 @@ describe('Unripe', function () { ) }) }) + + + // ### Example 3: balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100 + // When all fertilizer is sold, balanceOfUnderlying is 100 tokens. + // Total Supply of unripeLP is 189. Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. + // If 25% of Fertilizer is sold, balanceOfUnderlying should be 25. + // We want the user to redeem 25%^2 = 6.25% of the underlying. + // If ~half the supply was chopped, they should get 3.30 tokens. + // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; + // redeem = 25 * 0.25 * 100/189 ~= 3.30 --> slighly more than with urBean due to small supply discrepancy + // since supply is smaller the user is entitled to more underlying per UnripeLP + + describe('LP chop half the supply, balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { + beforeEach(async function () { + // totalDollarsneeded = 47 + await this.unripeLP.mint(userAddress, to6('189')) + await this.unripe.connect(owner).addUnderlying( + UNRIPE_LP, + to6('25') // balanceOfUnderlying is 25 + ) + // s.recapitalized=25 + await this.fertilizer.connect(user).setPenaltyParams(to6('25'), to6('100')) + + // remaining recapitalization = totalDollarsNeeded - s.recapitalized = 100 - 25 = 75 + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('75')) + // s.recapitalized = 25 + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('25')) + + // user chops ~ half the unripe LP supply + this.result = await this.unripe.connect(user).chop(UNRIPE_LP, to6('100'), EXTERNAL, EXTERNAL) + + }) + + it('getters', async function () { + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_LP)).to.be.equal(to6('0.243742')) + expect(await this.unripe.getPenalty(UNRIPE_LP)).to.be.equal(to6('0.112500')) + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.112500')) + expect(await this.unripe.getUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.243742')) + expect(await this.unripe.balanceOfUnderlying(UNRIPE_LP, userAddress)).to.be.equal(to6('21.693122')) + // 89 * 0.112500 = 10.0125 + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_LP, userAddress)).to.be.equal(to6('10.012586')) + expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_LP)).to.be.equal(to6('21.693122')) + expect(await this.unripe.isUnripe(UNRIPE_LP)).to.be.equal(true) + }) + + it('reduces s.recapitalized proportionally to the amount LP chopped', async function () { + // recapitalization has reduced proportionally to the dollar amount of unripe LP chopped + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('21.693122')) + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('25.306878')) + }) + + it('changes balaces', async function () { + expect(await this.unripeLP.balanceOf(userAddress)).to.be.equal(to6('89')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.306878')); + expect(await this.unripeLP.totalSupply()).to.be.equal(to6('89')); + // 25 underlying at the start - 3.306878 redeemed = 21.693122 + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('21.693122')) + }) + + it('emits an event', async function () { + await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( + user.address, + UNRIPE_LP, + to6('100'), + to6('3.306878') + ) + }) + }) // Same as above but with different transfer modes used describe('chop, different transfer modes, half the supply, balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { From 361a739f7bd89f65d62cb6ced9c99acfbb03320d Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 25 Apr 2024 20:28:43 +0300 Subject: [PATCH 48/86] Add upgrade bip script blueprint --- .../init/initBipMiscImprovements.sol | 19 +++++++ protocol/contracts/libraries/LibChop.sol | 3 +- protocol/scripts/bips.js | 49 ++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 protocol/contracts/beanstalk/init/initBipMiscImprovements.sol diff --git a/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol b/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol new file mode 100644 index 0000000000..ca5fdae6ce --- /dev/null +++ b/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol @@ -0,0 +1,19 @@ +/* + SPDX-License-Identifier: MIT +*/ + +pragma solidity =0.7.6; +pragma experimental ABIEncoderV2; + +import "../../C.sol"; + +/** + * @author deadmanwalking + * @title InitBipMiscImprovements runs the code for BIP InitBipMiscImprovements + **/ +contract InitBipMiscImprovements { + + function init() external { + // Do the work needed for the BIP + } +} diff --git a/protocol/contracts/libraries/LibChop.sol b/protocol/contracts/libraries/LibChop.sol index 12c521809e..bc2bfa87ad 100644 --- a/protocol/contracts/libraries/LibChop.sol +++ b/protocol/contracts/libraries/LibChop.sol @@ -31,7 +31,8 @@ library LibChop { ) internal returns (address underlyingToken, uint256 underlyingAmount) { AppStorage storage s = LibAppStorage.diamondStorage(); underlyingAmount = LibUnripe.getPenalizedUnderlying(unripeToken, amount, supply); - LibUnripe.decrementUnderlying(unripeToken, underlyingAmount); + // remove the underlying amount and decrease s.recapitalized if token is unripe LP + LibUnripe.removeUnderlying(unripeToken, underlyingAmount); underlyingToken = s.u[unripeToken].underlyingToken; } } diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index b9c92bba1b..e3971c49af 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -308,7 +308,6 @@ async function bipMigrateUnripeBeanEthToBeanSteth(mock = true, account = undefin verify: false }); - if (oracleAccount == undefined) { oracleAccount = await impersonateSigner('0x30a1976d5d087ef0BA0B4CDe87cc224B74a9c752', true); // Oracle deployer await mintEth(oracleAccount.address); @@ -316,6 +315,51 @@ async function bipMigrateUnripeBeanEthToBeanSteth(mock = true, account = undefin await deployContract('UsdOracle', oracleAccount, verbose) } +async function bipMiscellaneousImprovements(mock = true, account = undefined, verbose = true) { + if (account == undefined) { + account = await impersonateBeanstalkOwner(); + await mintEth(account.address); + } + + await upgradeWithNewFacets({ + diamondAddress: BEANSTALK, + facetNames: [ + "UnripeFacet", + "ConvertFacet", + "SeasonFacet", + ], + libraryNames: [ + 'LibChopConvert', + 'LibConvert', + 'LibConvertData', + 'LibLambdaConvert', + 'LibChop', + 'LibFertilizer', + 'LibStrings', + 'LibUnripe', + 'LibWellMinting', + ], + facetLibraries: { + 'UnripeFacet': [ + 'LibUnripe', + 'LibChop' + ], + 'ConvertFacet': [ + 'LibConvert', + ], + 'SeasonFacet': [ + 'LibWellMinting', + ] + }, + initFacetName: "InitMiscellaneousImprovements", + selectorsToRemove: [], + bip: false, + object: !mock, + verbose: verbose, + account: account, + verify: false + }); +} exports.bip29 = bip29 exports.bip30 = bip30 @@ -325,4 +369,5 @@ exports.bipBasinIntegration = bipBasinIntegration exports.bipSeedGauge = bipSeedGauge exports.mockBeanstalkAdmin = mockBeanstalkAdmin exports.bipMigrateUnripeBean3CrvToBeanEth = bipMigrateUnripeBean3CrvToBeanEth -exports.bipMigrateUnripeBeanEthToBeanSteth = bipMigrateUnripeBeanEthToBeanSteth \ No newline at end of file +exports.bipMigrateUnripeBeanEthToBeanSteth = bipMigrateUnripeBeanEthToBeanSteth +exports.bipMiscellaneousImprovements = bipMiscellaneousImprovements \ No newline at end of file From 9ee5f4fce81785e113aae7d845da739e7c2638fd Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 26 Apr 2024 10:04:21 +0300 Subject: [PATCH 49/86] Add recapitalization tests for urBean chops --- protocol/test/Unripe.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 960853ad33..8de685f58e 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -212,6 +212,11 @@ describe('Unripe', function () { expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('12.5')) }) + it('urBean chop does not affect recapitalization', async function () { + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('25')) + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('25')) + }) + it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, @@ -278,6 +283,11 @@ describe('Unripe', function () { expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('9.375')) }) + it('urBean chop does not affect recapitalization', async function () { + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('12.5')) + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('37.5')) + }) + it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, @@ -342,6 +352,11 @@ describe('Unripe', function () { expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('21.875')) }) + it('urBean chop does not affect recapitalization', async function () { + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('25')) + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('75')) + }) + it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, From 2bbb4c597476a9fc43d2b7fd267f24b52e8010ba Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Fri, 26 Apr 2024 10:20:56 +0300 Subject: [PATCH 50/86] clean up --- .../contracts/libraries/LibFertilizer.sol | 1 - protocol/contracts/libraries/LibUnripe.sol | 24 +++++++------------ protocol/test/Unripe.test.js | 13 +++------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 93953491c7..9453d21490 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -18,7 +18,6 @@ import {LibDiamond} from "contracts/libraries/LibDiamond.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import {LibWell} from "contracts/libraries/Well/LibWell.sol"; import {LibUsdOracle} from "contracts/libraries/Oracle/LibUsdOracle.sol"; -import "hardhat/console.sol"; /** * @author Publius diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 85199b6ec7..1fe5a62d65 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -13,7 +13,7 @@ import {Call, IWell} from "contracts/interfaces/basin/IWell.sol"; import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; import {LibFertilizer} from "./LibFertilizer.sol"; -import "hardhat/console.sol"; + /** * @title LibUnripe * @author Publius @@ -153,23 +153,17 @@ library LibUnripe { ) internal view returns (uint256 redeem) { require(isUnripe(unripeToken), "not vesting"); AppStorage storage s = LibAppStorage.diamondStorage(); - uint256 usdValueRaised = s.recapitalized; - // !!!!!!!!!!!!!! getTotalRecapDollarsNeeded() queries for the total urLP supply which is burned in UnripeFacet.sol - // TODO: Modify getTotalRecapDollarsNeeded() to accept the total LP supply as a parameter? - // note: Supply here is the total supply querited before the burn - uint256 totalUsdNeeded = unripeToken == C.UNRIPE_LP ? LibFertilizer.getTotalRecapDollarsNeeded(supply) : LibFertilizer.getTotalRecapDollarsNeeded(); - uint256 currentRipeUnderlying = s.u[unripeToken].balanceOfUnderlying; - console.log("currentRipeUnderlying", currentRipeUnderlying); - console.log("usdValueRaised", usdValueRaised); - console.log("totalUsdNeeded", totalUsdNeeded); - console.log("amount", amount); - console.log("supply", supply); - console.log("s.recapitalized", s.recapitalized); - // total redeemable * %DollarRecapitalized^2 * share of unripe tokens + // getTotalRecapDollarsNeeded() queries for the total urLP supply which is burned upon a chop + // If the token being chopped is unripeLP, getting the current supply here is inaccurate due to the burn + // Instead, we use the supply passed in as an argument to getTotalRecapDollarsNeeded since the supply variable + // here is the total urToken supply queried before burnning the unripe token + uint256 totalUsdNeeded = unripeToken == C.UNRIPE_LP ? LibFertilizer.getTotalRecapDollarsNeeded(supply) + : LibFertilizer.getTotalRecapDollarsNeeded(); + // chop rate = total redeemable * %DollarRecapitalized^2 * share of unripe tokens // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying // So eventually redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply - redeem = currentRipeUnderlying.mul(usdValueRaised).div(totalUsdNeeded).mul(amount).div(supply); + redeem = s.u[unripeToken].balanceOfUnderlying.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); } /** diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 8de685f58e..e81a68226b 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -154,7 +154,6 @@ describe('Unripe', function () { //////////////////////////////////// CHOPS ////////////////////////////////////////////// - //////////////// THIS PASSES ////////////////////// // Example 2: balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100 // When all fertilizer is sold, balanceOfUnderlying is 50 tokens (totalusdneeded = 50) @@ -185,9 +184,6 @@ describe('Unripe', function () { await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) // user chops the whole unripe bean supply this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) - - // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; - // redeem = 25 * 0.5 * 100/100 = 12.5 }) it('getters', async function () { @@ -227,8 +223,8 @@ describe('Unripe', function () { }) }) - //////////////// THIS PASSES ////////////////////// - // Still Example 2 + // Still Example 2: + // When all fertilizer is sold, balanceOfUnderlying is 50 tokens. // Total Supply of unripe is 100. // Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. @@ -258,8 +254,6 @@ describe('Unripe', function () { await this.fertilizer.connect(owner).setPenaltyParams(to6('12.5'), to6('100')) // user chops the whole unripe bean supply this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) - // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; - // redeem = 12.5 * 0.25 * 100/100 = 3.125 }) it('getters', async function () { @@ -271,6 +265,7 @@ describe('Unripe', function () { // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); + expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('9.375')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) @@ -324,8 +319,6 @@ describe('Unripe', function () { await this.fertilizer.connect(owner).setPenaltyParams(to6('25'), to6('100')) // user chops half the unripe bean supply this.result = await this.unripe.connect(user).chop(UNRIPE_BEAN, to6('100'), EXTERNAL, EXTERNAL) - // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; - // redeem = 25 * 0.25 * 100/200 = 3.125 }) it('getters', async function () { From d0a660c8a8d1300fc16ef4a077f395f7a98726e4 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 28 Apr 2024 17:26:02 +0300 Subject: [PATCH 51/86] update tests, small cleanup --- protocol/abi/Beanstalk.json | 47 +++------- .../beanstalk/barn/FertilizerFacet.sol | 7 ++ .../libraries/Convert/LibConvert.sol | 3 - .../contracts/libraries/LibFertilizer.sol | 10 +-- protocol/contracts/libraries/LibUnripe.sol | 10 ++- protocol/hardhat.config.js | 2 +- protocol/lib/forge-std | 2 +- protocol/test/Unripe.test.js | 88 ++++++++----------- 8 files changed, 71 insertions(+), 98 deletions(-) diff --git a/protocol/abi/Beanstalk.json b/protocol/abi/Beanstalk.json index ac5b50657e..f96fd0644f 100644 --- a/protocol/abi/Beanstalk.json +++ b/protocol/abi/Beanstalk.json @@ -118,35 +118,6 @@ "name": "SwitchUnderlyingToken", "type": "event" }, - { - "inputs": [ - { - "internalType": "address", - "name": "unripeToken", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "supply", - "type": "uint256" - } - ], - "name": "_getPenalizedUnderlying", - "outputs": [ - { - "internalType": "uint256", - "name": "redeem", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -749,6 +720,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "well", + "type": "address" + } + ], + "name": "beginBarnRaiseMigration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -988,11 +972,6 @@ "internalType": "uint256", "name": "minLPTokensOut", "type": "uint256" - }, - { - "internalType": "enum LibTransfer.From", - "name": "mode", - "type": "uint8" } ], "name": "mintFertilizer", diff --git a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol index e21d4eb721..aa007e1861 100644 --- a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol +++ b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol @@ -253,4 +253,11 @@ contract FertilizerFacet { LibDiamond.enforceIsOwnerOrContract(); LibFertilizer.beginBarnRaiseMigration(well); } + + /** + * @notice returns the total recapitalization dollars needed to recapitalize the Barn Raise. + */ + function getTotalRecapDollarsNeeded() external view returns (uint256) { + return LibFertilizer.getTotalRecapDollarsNeeded(); + } } diff --git a/protocol/contracts/libraries/Convert/LibConvert.sol b/protocol/contracts/libraries/Convert/LibConvert.sol index fde3844564..5fb4fe9148 100644 --- a/protocol/contracts/libraries/Convert/LibConvert.sol +++ b/protocol/contracts/libraries/Convert/LibConvert.sol @@ -47,9 +47,6 @@ library LibConvert { { LibConvertData.ConvertKind kind = convertData.convertKind(); - // if (kind == LibConvertData.ConvertKind.BEANS_TO_CURVE_LP) { - // (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibCurveConvert - // .convertBeansToLP(convertData); if (kind == LibConvertData.ConvertKind.BEANS_TO_WELL_LP) { (cp.toToken, cp.fromToken, cp.toAmount, cp.fromAmount) = LibWellConvert .convertBeansToLP(convertData); diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 9453d21490..edb356f2dd 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -229,20 +229,16 @@ library LibFertilizer { * @return totalDollars The total dollar amount. */ function getTotalRecapDollarsNeeded() internal view returns(uint256) { - uint256 totalDollars = C - .dollarPerUnripeLP() - .mul(C.unripeLP().totalSupply()) - .div(DECIMALS); - totalDollars = totalDollars / 1e6 * 1e6; // round down to nearest USDC - return totalDollars; + return getTotalRecapDollarsNeeded(C.unripeLP().totalSupply()); } /** * @dev Returns the total dollar amount needed to recapitalize Beanstalk * for the supply of Unripe LP. + * @param urLPsupply The supply of Unripe LP. * @return totalDollars The total dollar amount. */ - function getTotalRecapDollarsNeeded(uint256 urLPsupply) internal view returns(uint256) { + function getTotalRecapDollarsNeeded(uint256 urLPsupply) internal pure returns(uint256) { uint256 totalDollars = C .dollarPerUnripeLP() .mul(urLPsupply) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 1fe5a62d65..326839763d 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -159,10 +159,10 @@ library LibUnripe { // here is the total urToken supply queried before burnning the unripe token uint256 totalUsdNeeded = unripeToken == C.UNRIPE_LP ? LibFertilizer.getTotalRecapDollarsNeeded(supply) : LibFertilizer.getTotalRecapDollarsNeeded(); - // chop rate = total redeemable * %DollarRecapitalized^2 * share of unripe tokens - // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; + // chop rate = total redeemable * (%DollarRecapitalized)^2 * share of unripe tokens + // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying - // So eventually redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply + // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply redeem = s.u[unripeToken].balanceOfUnderlying.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); } @@ -229,4 +229,8 @@ library LibUnripe { AppStorage storage s = LibAppStorage.diamondStorage(); unripe = s.u[unripeToken].underlyingToken != address(0); } + + function getTotalRecapDollarsNeeded() internal view returns (uint256 totalUsdNeeded) { + return LibFertilizer.getTotalRecapDollarsNeeded(); + } } diff --git a/protocol/hardhat.config.js b/protocol/hardhat.config.js index 7906f64425..44fc8e12e9 100644 --- a/protocol/hardhat.config.js +++ b/protocol/hardhat.config.js @@ -309,7 +309,7 @@ module.exports = { version: "0.7.6", settings: { optimizer: { - enabled: true, + enabled: false, runs: 100 } } diff --git a/protocol/lib/forge-std b/protocol/lib/forge-std index 4a79aca83f..bb4ceea94d 160000 --- a/protocol/lib/forge-std +++ b/protocol/lib/forge-std @@ -1 +1 @@ -Subproject commit 4a79aca83f8075f8b1b4fe9153945fef08375630 +Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index e81a68226b..fe54f84bb4 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -9,7 +9,7 @@ const ZERO_BYTES = ethers.utils.formatBytes32String('0x0') let user, user2, owner; let userAddress, ownerAddress, user2Address; -describe('Unripe', function () { +describe.only('Unripe', function () { before(async function () { [owner, user, user2] = await ethers.getSigners() userAddress = user.address; @@ -164,13 +164,13 @@ describe('Unripe', function () { // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; // redeem = 25 * 0.5 * 100/100 = 12.5 - describe('chop whole supply with balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100, fert 50% sold', async function () { beforeEach(async function () { // we need total dollars needed to be 50 * 1e6 // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS // 50 * 1e6 = 530618 * supply / 1e6 // solve for supply --> we get 94229747,2 --> round to nearest usdc = 95 + // (contract rounds down, thus we round up when issuing unripeLP tokens). await this.unripeLP.mint(userAddress, to6('95')) // unripe bean supply == 100 await this.unripeBean.mint(userAddress, to6('100')) @@ -187,21 +187,14 @@ describe('Unripe', function () { }) it('getters', async function () { - // this divides by total supply so ---> "SafeMath: division by zero" when chop amount == total supply - // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) - // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) - // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) - // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) - // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) - - // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); + // fertilizer recapitalization is independent of the recapitalization of unripe. expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('12.5')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) }) - it('changes balaces', async function () { + it('changes balances', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('0')) expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('12.5')); expect(await this.unripeBean.totalSupply()).to.be.equal(to6('0')); @@ -234,7 +227,6 @@ describe('Unripe', function () { // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; // redeem = 12.5 * 0.25 * 100/100 = 3.125 - describe('chop whole supply with balanceOfUnderlying Max ≠ Unripe Total Supply, balanceOfUnderlying < 100, fert 25% sold', async function () { beforeEach(async function () { // we need total dollars needed to be 50 * 1e6 @@ -257,21 +249,13 @@ describe('Unripe', function () { }) it('getters', async function () { - // this divides by total supply so ---> "SafeMath: division by zero" when chop amount == total supply - // expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_BEAN)).to.be.equal('100090') - // expect(await this.unripe.getPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.010018')) - // expect(await this.unripe.getPenalizedUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.010018')) - // expect(await this.unripe.getUnderlying(UNRIPE_BEAN, to6('1'))).to.be.equal(to6('0.10009')) - // expect(await this.unripe.balanceOfUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('99.99')) - // expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_BEAN, userAddress)).to.be.equal(to6('10.008008')) - // s.fertilizedIndex.mul(amount).div(s.unfertilizedIndex); - expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) + // 12.5 - 3.125 = 9.375 expect(await this.unripe.getTotalUnderlying(UNRIPE_BEAN)).to.be.equal(to6('9.375')) expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) }) - it('changes balaces', async function () { + it('changes balances', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('0')); expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.125')); expect(await this.unripeBean.totalSupply()).to.be.equal(to6('0')); @@ -302,7 +286,6 @@ describe('Unripe', function () { // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; // redeem = 25 * 0.25 * 100/200 = 3.125 - describe('chop half the supply balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { beforeEach(async function () { // but totalDollarsneeded = dollarPerUnripeLP * C.unripeLP().totalSupply() / DECIMALS @@ -337,7 +320,7 @@ describe('Unripe', function () { expect(await this.unripe.isUnripe(UNRIPE_BEAN)).to.be.equal(true) }) - it('changes balaces', async function () { + it('changes balances', async function () { expect(await this.unripeBean.balanceOf(userAddress)).to.be.equal(to6('100')) expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.125')); expect(await this.unripeBean.totalSupply()).to.be.equal(to6('100')); @@ -362,15 +345,14 @@ describe('Unripe', function () { // ### Example 3: balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100 - // When all fertilizer is sold, balanceOfUnderlying is 100 tokens. + // When all fertilizer is sold, balanceOfUnderlying is 189 tokens. // Total Supply of unripeLP is 189. Assume 1 Fertilizer increases balanceOfUnderlying by 1 token. // If 25% of Fertilizer is sold, balanceOfUnderlying should be 25. // We want the user to redeem 25%^2 = 6.25% of the underlying. - // If ~half the supply was chopped, they should get 3.30 tokens. + // If ~half the supply was chopped, they should get 3.125 tokens. // formula: redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply; - // redeem = 25 * 0.25 * 100/189 ~= 3.30 --> slighly more than with urBean due to small supply discrepancy + // redeem = 25 * 0.25 * 1/2 ~= 3.125 --> slightly more than with urBean due to small supply discrepancy // since supply is smaller the user is entitled to more underlying per UnripeLP - describe('LP chop half the supply, balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { beforeEach(async function () { // totalDollarsneeded = 47 @@ -386,45 +368,53 @@ describe('Unripe', function () { expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('75')) // s.recapitalized = 25 expect(await this.unripe.getRecapitalized()).to.be.equal(to6('25')) - + + // 25 * 0.25 * 1/189 ~= 0.03306878307 + expect(await this.unripe.getPenalty(UNRIPE_LP)).to.be.equal(to6('0.033068')) // user chops ~ half the unripe LP supply - this.result = await this.unripe.connect(user).chop(UNRIPE_LP, to6('100'), EXTERNAL, EXTERNAL) + this.result = await this.unripe.connect(user).chop(UNRIPE_LP, to6('94.5'), EXTERNAL, EXTERNAL) }) it('getters', async function () { - expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_LP)).to.be.equal(to6('0.243742')) - expect(await this.unripe.getPenalty(UNRIPE_LP)).to.be.equal(to6('0.112500')) - expect(await this.unripe.getPenalizedUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.112500')) - expect(await this.unripe.getUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.243742')) - expect(await this.unripe.balanceOfUnderlying(UNRIPE_LP, userAddress)).to.be.equal(to6('21.693122')) - // 89 * 0.112500 = 10.0125 - expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_LP, userAddress)).to.be.equal(to6('10.012586')) + // 21.875 / 94.5 + expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_LP)).to.be.equal(to6('0.231481')) + // 21.876 * 0.43752 * 1/94.5 ~= 0.03306878307 + expect(await this.unripe.getPenalty(UNRIPE_LP)).to.be.equal(to6('0.101273')) + + expect(await this.unripe.getPenalizedUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.101273')) + expect(await this.unripe.getUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.231481')) + expect(await this.unripe.balanceOfUnderlying(UNRIPE_LP, userAddress)).to.be.equal(to6('21.875000')) + // 94.5 * 0.101273 = 9.5702985 + expect(await this.unripe.balanceOfPenalizedUnderlying(UNRIPE_LP, userAddress)).to.be.equal(to6('9.570312')) expect(await this.unripe.getRecapPaidPercent()).to.be.equal(to6('0.01')) - expect(await this.unripe.getTotalUnderlying(UNRIPE_LP)).to.be.equal(to6('21.693122')) + expect(await this.unripe.getTotalUnderlying(UNRIPE_LP)).to.be.equal(to6('21.875000')) expect(await this.unripe.isUnripe(UNRIPE_LP)).to.be.equal(true) }) it('reduces s.recapitalized proportionally to the amount LP chopped', async function () { // recapitalization has reduced proportionally to the dollar amount of unripe LP chopped - expect(await this.unripe.getRecapitalized()).to.be.equal(to6('21.693122')) - expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('25.306878')) + // 21.875000/25 = 0.87500000 + // 12.5% or 3.125 + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('21.875000')) + // 50 - 21.875 = 28.125 + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('28.125000')) }) - it('changes balaces', async function () { - expect(await this.unripeLP.balanceOf(userAddress)).to.be.equal(to6('89')) - expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.306878')); - expect(await this.unripeLP.totalSupply()).to.be.equal(to6('89')); - // 25 underlying at the start - 3.306878 redeemed = 21.693122 - expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('21.693122')) + it('changes balances', async function () { + expect(await this.unripeLP.balanceOf(userAddress)).to.be.equal(to6('94.5')) + expect(await this.bean.balanceOf(userAddress)).to.be.equal(to6('3.125')); + expect(await this.unripeLP.totalSupply()).to.be.equal(to6('94.5')); + // 25 underlying at the start - 3.125 redeemed = 21.875000 + expect(await this.bean.balanceOf(this.unripe.address)).to.be.equal(to6('21.875000')) }) it('emits an event', async function () { await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( user.address, UNRIPE_LP, - to6('100'), - to6('3.306878') + to6('94.5'), + to6('3.125') ) }) }) From 54cc4d16d1e568d9ca00692005beef323ad8c422 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 28 Apr 2024 17:38:36 +0300 Subject: [PATCH 52/86] add bip, remove .only --- .../init/initBipMiscImprovements.sol | 19 ------------------- protocol/scripts/bips.js | 2 +- protocol/test/Unripe.test.js | 2 +- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 protocol/contracts/beanstalk/init/initBipMiscImprovements.sol diff --git a/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol b/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol deleted file mode 100644 index ca5fdae6ce..0000000000 --- a/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol +++ /dev/null @@ -1,19 +0,0 @@ -/* - SPDX-License-Identifier: MIT -*/ - -pragma solidity =0.7.6; -pragma experimental ABIEncoderV2; - -import "../../C.sol"; - -/** - * @author deadmanwalking - * @title InitBipMiscImprovements runs the code for BIP InitBipMiscImprovements - **/ -contract InitBipMiscImprovements { - - function init() external { - // Do the work needed for the BIP - } -} diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index e3971c49af..5b19d1fedb 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -327,6 +327,7 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve "UnripeFacet", "ConvertFacet", "SeasonFacet", + "FertilizerFacet" ], libraryNames: [ 'LibChopConvert', @@ -351,7 +352,6 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve 'LibWellMinting', ] }, - initFacetName: "InitMiscellaneousImprovements", selectorsToRemove: [], bip: false, object: !mock, diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index fe54f84bb4..3c4fb58dd4 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -9,7 +9,7 @@ const ZERO_BYTES = ethers.utils.formatBytes32String('0x0') let user, user2, owner; let userAddress, ownerAddress, user2Address; -describe.only('Unripe', function () { +describe('Unripe', function () { before(async function () { [owner, user, user2] = await ethers.getSigners() userAddress = user.address; From 3ebb8135d9ef6f1bcd39ea1cdde8d995d6f13432 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Mon, 29 Apr 2024 14:53:39 +0300 Subject: [PATCH 53/86] Add bip scirpts --- .../init/initBipMiscImprovements.sol | 19 +++++++++++++++++++ protocol/hardhat.config.js | 9 +++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 protocol/contracts/beanstalk/init/initBipMiscImprovements.sol diff --git a/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol b/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol new file mode 100644 index 0000000000..ae2c9e3302 --- /dev/null +++ b/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol @@ -0,0 +1,19 @@ +/* + SPDX-License-Identifier: MIT +*/ + +pragma solidity =0.7.6; +pragma experimental ABIEncoderV2; + +import "../../C.sol"; + +/** + * @author deadmanwalking + * @title InitBipMiscImprovements runs the code for BIP InitBipMiscImprovements + **/ +contract InitBipMiscImprovements { + + function init() external { + // Do the work needed for the BIP + } +} \ No newline at end of file diff --git a/protocol/hardhat.config.js b/protocol/hardhat.config.js index 44fc8e12e9..811919f41e 100644 --- a/protocol/hardhat.config.js +++ b/protocol/hardhat.config.js @@ -14,7 +14,8 @@ require("@nomiclabs/hardhat-etherscan"); // BIP 39 const { bipSeedGauge } = require("./scripts/bips.js"); -// +// BIP Misc Improvements +const { bipMiscellaneousImprovements } = require("./scripts/bips.js"); const { upgradeWithNewFacets } = require("./scripts/diamond"); const { @@ -225,6 +226,10 @@ task("deployBip39", async function () { await bipSeedGauge(); }); +task("deployBipMiscImprovements", async function () { + await bipMiscellaneousImprovements(); +}); + task("ebip14", async function () { await ebip14(); }) @@ -318,7 +323,7 @@ module.exports = { version: "0.8.17", settings: { optimizer: { - enabled: true, + enabled: false, runs: 1000 } } From 6dc27aed05fdbce73a2439d973eb3bb3031418e9 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Mon, 29 Apr 2024 16:50:42 +0300 Subject: [PATCH 54/86] Remove init script --- .../init/initBipMiscImprovements.sol | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 protocol/contracts/beanstalk/init/initBipMiscImprovements.sol diff --git a/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol b/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol deleted file mode 100644 index ae2c9e3302..0000000000 --- a/protocol/contracts/beanstalk/init/initBipMiscImprovements.sol +++ /dev/null @@ -1,19 +0,0 @@ -/* - SPDX-License-Identifier: MIT -*/ - -pragma solidity =0.7.6; -pragma experimental ABIEncoderV2; - -import "../../C.sol"; - -/** - * @author deadmanwalking - * @title InitBipMiscImprovements runs the code for BIP InitBipMiscImprovements - **/ -contract InitBipMiscImprovements { - - function init() external { - // Do the work needed for the BIP - } -} \ No newline at end of file From b2cc9e55fd633625de0fc09ad46b7d654e0fb737 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 1 May 2024 10:42:16 +0100 Subject: [PATCH 55/86] cap redeem to balanceOfUnderlying --- protocol/contracts/libraries/LibUnripe.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 326839763d..68187e4eb8 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -163,7 +163,11 @@ library LibUnripe { // redeem = totalRipeUnderlying * (usdValueRaised/totalUsdNeeded)^2 * UnripeAmountIn/UnripeSupply; // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply - redeem = s.u[unripeToken].balanceOfUnderlying.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); + uint256 underlyingAmount = s.u[unripeToken].balanceOfUnderlying; + redeem = underlyingAmount.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); + // cap `redeem to `balanceOfUnderlying in the case that `s.recapitalized` exceeds `totalUsdNeeded`. + // this can occur due to unripe LP chops. + if(redeem > underlyingAmount) redeem = underlyingAmount; } /** From 901606c149948fdf2bc92aa749e1c5c49aba1f0e Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 2 May 2024 12:58:35 +0100 Subject: [PATCH 56/86] update libLockedUnderlying to accept %recap rather than % fert paid back --- protocol/abi/Beanstalk.json | 26 ++++++++++++++++ .../contracts/beanstalk/barn/UnripeFacet.sol | 9 +++++- .../contracts/libraries/LibFertilizer.sol | 3 ++ .../libraries/LibLockedUnderlying.sol | 5 +++- protocol/contracts/libraries/LibUnripe.sol | 24 +++++++++++++-- .../mocks/mockFacets/MockUnripeFacet.sol | 4 --- protocol/hardhat.config.js | 30 ++++++++++++++++++- protocol/test/Gauge.test.js | 2 +- protocol/test/SeedGaugeMainnet.test.js | 4 +-- 9 files changed, 94 insertions(+), 13 deletions(-) diff --git a/protocol/abi/Beanstalk.json b/protocol/abi/Beanstalk.json index f96fd0644f..f82a3ee299 100644 --- a/protocol/abi/Beanstalk.json +++ b/protocol/abi/Beanstalk.json @@ -374,6 +374,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getRecapitalized", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -943,6 +956,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getTotalRecapDollarsNeeded", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "isFertilizing", diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index af9cbe753b..e84b51970d 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -356,7 +356,7 @@ contract UnripeFacet is ReentrancyGuard { function getLockedBeansUnderlyingUnripeBean() external view returns (uint256) { return LibLockedUnderlying.getLockedUnderlying( C.UNRIPE_BEAN, - LibUnripe.getRecapPaidPercentAmount(1e6) + LibUnripe.getTotalRecapitalizedPercent() ); } @@ -367,4 +367,11 @@ contract UnripeFacet is ReentrancyGuard { uint256[] memory twaReserves = LibWell.getTwaReservesFromBeanstalkPump(LibBarnRaise.getBarnRaiseWell()); return LibUnripe.getLockedBeansFromLP(twaReserves); } + + /** + * @notice returns the amount of dollars recapitalized in the barn raise. + */ + function getRecapitalized() external view returns (uint256) { + return s.recapitalized; + } } diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index edb356f2dd..43b73c28ca 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -19,6 +19,8 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import {LibWell} from "contracts/libraries/Well/LibWell.sol"; import {LibUsdOracle} from "contracts/libraries/Oracle/LibUsdOracle.sol"; +// import "hardhat/console.sol"; + /** * @author Publius * @title Fertilizer @@ -229,6 +231,7 @@ library LibFertilizer { * @return totalDollars The total dollar amount. */ function getTotalRecapDollarsNeeded() internal view returns(uint256) { + // console.log("urLPSupply:", C.unripeLP().totalSupply()); return getTotalRecapDollarsNeeded(C.unripeLP().totalSupply()); } diff --git a/protocol/contracts/libraries/LibLockedUnderlying.sol b/protocol/contracts/libraries/LibLockedUnderlying.sol index 4f10618ff0..6808769009 100644 --- a/protocol/contracts/libraries/LibLockedUnderlying.sol +++ b/protocol/contracts/libraries/LibLockedUnderlying.sol @@ -6,7 +6,7 @@ pragma experimental ABIEncoderV2; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; - +// import "hardhat/console.sol"; /** * @title LibLockedUnderlying * @author Brendan @@ -25,6 +25,9 @@ library LibLockedUnderlying { uint256 recapPercentPaid ) external view returns (uint256 lockedUnderlying) { AppStorage storage s = LibAppStorage.diamondStorage(); + // console.log("token:", unripeToken); + // console.log("balance of underlying:", s.u[unripeToken].balanceOfUnderlying); + // console.log("get percent locked underlying:", getPercentLockedUnderlying(unripeToken, recapPercentPaid)); return s .u[unripeToken] diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 68187e4eb8..5d0c4e49af 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -14,6 +14,8 @@ import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; import {LibFertilizer} from "./LibFertilizer.sol"; +// import "hardhat/console.sol"; + /** * @title LibUnripe * @author Publius @@ -164,12 +166,28 @@ library LibUnripe { // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply uint256 underlyingAmount = s.u[unripeToken].balanceOfUnderlying; + // 18 * 6 / 18 * 18 / 18 = 6 + // 6 * 6 / 18 * 18 / 18 = 6 redeem = underlyingAmount.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); // cap `redeem to `balanceOfUnderlying in the case that `s.recapitalized` exceeds `totalUsdNeeded`. // this can occur due to unripe LP chops. if(redeem > underlyingAmount) redeem = underlyingAmount; } + /** + * @notice returns the total percentage that beanstalk has recapitalized. + * @dev this is calculated by the ratio of s.recapitalized and the total dollars the barnraise needs to raise. + * returns the same precision as `getRecapPaidPercentAmount` (100% recapitalized = 1e6). + */ + function getTotalRecapitalizedPercent() internal view returns (uint256 recapitalizedPercent) { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); + // console.log("totalUsdNeeded:", totalUsdNeeded); + // console.log("s.recapitalized:", s.recapitalized); + if(totalUsdNeeded == 0) return 0; + return s.recapitalized.mul(DECIMALS).div(totalUsdNeeded); + } + /** * @notice Returns the amount of beans that are locked in the unripe token. * @dev Locked beans are the beans that are forfeited if the unripe token is chopped. @@ -180,7 +198,7 @@ library LibUnripe { uint256[] memory reserves ) internal view returns (uint256 lockedAmount) { lockedAmount = LibLockedUnderlying - .getLockedUnderlying(C.UNRIPE_BEAN, getRecapPaidPercentAmount(1e6)) + .getLockedUnderlying(C.UNRIPE_BEAN, getTotalRecapitalizedPercent()) .add(getLockedBeansFromLP(reserves)); } @@ -195,10 +213,10 @@ library LibUnripe { // if reserves return 0, then skip calculations. if (reserves[0] == 0) return 0; - + // console.log("get recap %:", getTotalRecapitalizedPercent()); uint256 lockedLpAmount = LibLockedUnderlying.getLockedUnderlying( C.UNRIPE_LP, - getRecapPaidPercentAmount(1e6) + getTotalRecapitalizedPercent() ); address underlying = s.u[C.UNRIPE_LP].underlyingToken; uint256 beanIndex = LibWell.getBeanIndexFromWell(underlying); diff --git a/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol b/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol index 6f6e77f886..5e3199fe3a 100644 --- a/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol @@ -20,10 +20,6 @@ contract MockUnripeFacet is UnripeFacet { s.u[unripeToken].merkleRoot = root; } - function getRecapitalized() external view returns (uint256) { - return s.recapitalized; - } - function addUnderlying(address unripeToken, uint256 amount) external payable diff --git a/protocol/hardhat.config.js b/protocol/hardhat.config.js index 811919f41e..fda9a6a038 100644 --- a/protocol/hardhat.config.js +++ b/protocol/hardhat.config.js @@ -250,6 +250,34 @@ task("ebip9", async function () { await ebip9(); }) +task("check", async function () { + const owner = await impersonateBeanstalkOwner(); + await mintEth(owner.address); + await upgradeWithNewFacets({ + diamondAddress: BEANSTALK, + facetNames: [ + "UnripeFacet", + "FertilizerFacet" + ], + libraryNames: [ + 'LibLockedUnderlying' + ], + facetLibraries: { + 'UnripeFacet': [ + 'LibLockedUnderlying' + ] + }, + initArgs: [], + bip: false, + verbose: true, + account: owner + }); + beanstalk = await getBeanstalk() + console.log("s.recapitalized:", await beanstalk.getRecapitalized()) + console.log("remaining recapitalization :",await beanstalk.remainingRecapitalization()) + console.log("total dollars needed to be raised:" , await beanstalk.getTotalRecapDollarsNeeded()) +}) + //////////////////////// SUBTASK CONFIGURATION //////////////////////// // Add a subtask that sets the action for the TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS task @@ -337,7 +365,7 @@ module.exports = { } }, gasReporter: { - enabled: true + enabled: false }, mocha: { timeout: 100000000 diff --git a/protocol/test/Gauge.test.js b/protocol/test/Gauge.test.js index 6f691f29d0..cf7037c450 100644 --- a/protocol/test/Gauge.test.js +++ b/protocol/test/Gauge.test.js @@ -65,7 +65,7 @@ describe('Gauge', function () { // add unripe this.unripeBean = await ethers.getContractAt('MockToken', UNRIPE_BEAN) this.unripeLP = await ethers.getContractAt('MockToken', UNRIPE_LP) - await this.unripeLP.mint(ownerAddress, to18('10000')) + await this.unripeLP.mint(ownerAddress, to6('10000')) await this.unripeBean.mint(ownerAddress, to6('10000')) await this.unripeLP.connect(owner).approve(this.diamond.address, to6('100000000')) await this.unripeBean.connect(owner).approve(this.diamond.address, to6('100000000')) diff --git a/protocol/test/SeedGaugeMainnet.test.js b/protocol/test/SeedGaugeMainnet.test.js index 11df6cc05f..560db20586 100644 --- a/protocol/test/SeedGaugeMainnet.test.js +++ b/protocol/test/SeedGaugeMainnet.test.js @@ -107,12 +107,12 @@ testIfRpcSet('SeedGauge Init Test', function () { expect(await this.beanstalk.getBeanToMaxLpGpPerBdvRatioScaled()).to.be.equal(to18('66.666666666666666666')); }) - it('lockedBeans', async function () { + it.skip('lockedBeans', async function () { // ~25.5m locked beans, ~35.8m total beans expect(await this.beanstalk.getLockedBeans()).to.be.within(to6('25900000.000000'), to6('26000000.000000')); }) - it('usd Liquidity', async function () { + it.skip('usd Liquidity', async function () { // ~13.2m usd liquidity in Bean:Eth expect(await this.beanstalk.getTwaLiquidityForWell(BEAN_ETH_WELL)).to.be.within(to18('13100000'), to18('13300000')); // ~13.2m usd liquidity in Bean:Eth From 4549a9f7501d33f0690b8f3df93eb57068fd5921 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 2 May 2024 13:00:17 +0100 Subject: [PATCH 57/86] update libLockedUnderlying from decimal precision update. --- .../libraries/LibLockedUnderlying.sol | 7 +- protocol/test/Gauge.test.js | 103 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/protocol/contracts/libraries/LibLockedUnderlying.sol b/protocol/contracts/libraries/LibLockedUnderlying.sol index 6808769009..bbd7042cd7 100644 --- a/protocol/contracts/libraries/LibLockedUnderlying.sol +++ b/protocol/contracts/libraries/LibLockedUnderlying.sol @@ -16,6 +16,8 @@ import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; library LibLockedUnderlying { using SafeMath for uint256; + uint256 constant DECIMALS = 1e6; + /** * @notice Return the amount of Underlying Tokens that would be locked if all of the Unripe Tokens * were chopped. @@ -55,12 +57,15 @@ library LibLockedUnderlying { * The equation is solved by using a lookup table of N_{⌈U/i⌉} values for different values of * U and R (The solution is independent of N) as solving iteratively is too computationally * expensive and there is no more efficient way to solve the equation. + * + * The lookup threshold assumes no decimal precision. This library only supports + * unripe tokens with 6 decimals. */ function getPercentLockedUnderlying( address unripeToken, uint256 recapPercentPaid ) private view returns (uint256 percentLockedUnderlying) { - uint256 unripeSupply = IERC20(unripeToken).totalSupply(); + uint256 unripeSupply = IERC20(unripeToken).totalSupply().div(DECIMALS); if (unripeSupply < 1_000_000) return 0; // If < 1,000,000 Assume all supply is unlocked. if (unripeSupply > 5_000_000) { if (unripeSupply > 10_000_000) { diff --git a/protocol/test/Gauge.test.js b/protocol/test/Gauge.test.js index cf7037c450..779a673305 100644 --- a/protocol/test/Gauge.test.js +++ b/protocol/test/Gauge.test.js @@ -315,6 +315,9 @@ describe('Gauge', function () { }) it('getters', async function () { + // issue unripe such that unripe supply > 10m. + await this.unripeLP.mint(ownerAddress, to6('10000000')) + await this.unripeBean.mint(ownerAddress, to6('10000000')) // urBean supply * 10% recapitalization (underlyingBean/UrBean) * 10% (fertilizerIndex/totalFertilizer) // = 10000 urBEAN * 10% = 1000 BEAN * (100-10%) = 900 beans locked. // urLP supply * 0.1% recapitalization (underlyingBEANETH/UrBEANETH) * 10% (fertilizerIndex/totalFertilizer) @@ -327,9 +330,109 @@ describe('Gauge', function () { expect( await this.seasonGetters.getLiquidityToSupplyRatio() ).to.be.eq(to18('1.000873426417975035')) + + }) + + it('< 1m unripe lockedBeans calculation:', async function () { + // current unripe LP and unripe Bean supply each: 10,000. + // under 1m unripe bean and LP, all supply is unlocked: + const getLockedBeansUnderlyingUnripeBean = await this.unripe.getLockedBeansUnderlyingUnripeBean() + const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeBeanEth() + const lockedBeans = await this.unripe.getLockedBeans() + const L2SR = await this.seasonGetters.getLiquidityToSupplyRatio() + + expect(getLockedBeansUnderlyingUnripeBean).to.be.eq('0') + expect(getLockedBeansUnderlyingUrLP).to.be.eq('0') + expect(lockedBeans).to.be.eq('0') + expect(L2SR).to.be.eq(to18('1')) + + // set urBean and urLP to 1m and verify values do not change: + await this.unripeLP.mint(ownerAddress, to6('989999')) + await this.unripeBean.mint(ownerAddress, to6('989999')) + + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(getLockedBeansUnderlyingUnripeBean) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(getLockedBeansUnderlyingUrLP) + expect(await this.unripe.getLockedBeans()).to.be.eq(lockedBeans) + expect(await this.seasonGetters.getLiquidityToSupplyRatio() + ).to.be.eq(L2SR) + }) + + it('< 5m unripe lockedBeans calculation:', async function () { + // mint unripe bean and LP such that 5m > supply > 1m. + await this.unripeLP.mint(ownerAddress, to6('1000000')) + await this.unripeBean.mint(ownerAddress, to6('1000000')) + + // verify locked beans amount changed: + const getLockedBeansUnderlyingUnripeBean = await this.unripe.getLockedBeansUnderlyingUnripeBean() + const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeBeanEth() + const lockedBeans = await this.unripe.getLockedBeans() + const L2SR = await this.seasonGetters.getLiquidityToSupplyRatio() + expect(getLockedBeansUnderlyingUnripeBean).to.be.eq(to6('579.500817')) + expect(getLockedBeansUnderlyingUrLP).to.be.eq(to6('579.500817')) + expect(lockedBeans).to.be.eq(to6('1159.001634')) + + // verify L2SR increased: + expect(L2SR).to.be.eq(to18('1.001160346477463386')) + + // set urBean and urLP to 5m and verify values do not change: + await this.unripeLP.mint(ownerAddress, to6('3990000')) + await this.unripeBean.mint(ownerAddress, to6('3990000')) + + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(getLockedBeansUnderlyingUnripeBean) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(getLockedBeansUnderlyingUrLP) + expect(await this.unripe.getLockedBeans()).to.be.eq(lockedBeans) + + expect(await this.seasonGetters.getLiquidityToSupplyRatio()).to.be.eq(L2SR) + }) + + it('< 10m unripe lockedBeans calculation:', async function () { + // mint unripe bean and LP such that 10m > supply > 5m. + await this.unripeLP.mint(ownerAddress, to6('5000000')) + await this.unripeBean.mint(ownerAddress, to6('5000000')) + + // verify locked beans amount changed: + const getLockedBeansUnderlyingUnripeBean = await this.unripe.getLockedBeansUnderlyingUnripeBean() + const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeBeanEth() + const lockedBeans = await this.unripe.getLockedBeans() + const L2SR = await this.seasonGetters.getLiquidityToSupplyRatio() + expect(getLockedBeansUnderlyingUnripeBean).to.be.eq(to6('515.604791')) + expect(getLockedBeansUnderlyingUrLP).to.be.eq(to6('515.604791')) + expect(lockedBeans).to.be.eq(to6('1031.209582')) + + // verify L2SR increased: + expect(L2SR).to.be.eq(to18('1.001032274072915240')) + + // set urBean and urLP to 10m and verify values do not change: + await this.unripeLP.mint(ownerAddress, to6('4990000')) + await this.unripeBean.mint(ownerAddress, to6('4990000')) + + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(getLockedBeansUnderlyingUnripeBean) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(getLockedBeansUnderlyingUrLP) + expect(await this.unripe.getLockedBeans()).to.be.eq(lockedBeans) + + expect(await this.seasonGetters.getLiquidityToSupplyRatio()).to.be.eq(L2SR) + }) + + it('< 10m unripe lockedBeans calculation:', async function () { + // mint unripe bean and LP such that supply > 10m. + await this.unripeLP.mint(ownerAddress, to6('10000000')) + await this.unripeBean.mint(ownerAddress, to6('10000000')) + + // verify locked beans amount changed: + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(to6('436.332105')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(to6('436.332105')) + expect(await this.unripe.getLockedBeans()).to.be.eq(to6('872.664210')) + + // verify L2SR increased: + expect( + await this.seasonGetters.getLiquidityToSupplyRatio() + ).to.be.eq(to18('1.000873426417975035')) }) it('is MEV resistant', async function () { + // issue unripe such that unripe supply > 10m. + await this.unripeLP.mint(ownerAddress, to6('10000000')) + await this.unripeBean.mint(ownerAddress, to6('10000000')) expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(to6('436.332105')) await this.well.mint(ownerAddress, to18('1000')) From 139ae317164fa7ab99ed90e97302d9f204c3c87f Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 3 May 2024 11:51:00 -0300 Subject: [PATCH 58/86] Update liblockedUnderlying. Uses `LibUnripe.getTotalRecapitalizedPercent()` instead of `LibUnripe.getRecapPaidPercentAmount`. - fixes various view functions - skipped legacy tests - fixed various tests - added mainnet tests. --- .../contracts/beanstalk/barn/UnripeFacet.sol | 15 +- .../contracts/libraries/LibFertilizer.sol | 2 - protocol/contracts/libraries/LibUnripe.sol | 6 - protocol/scripts/bips.js | 27 +-- .../test/BeanEthToBeanWstethMigration.test.js | 2 +- protocol/test/Gauge.test.js | 57 ++++++- protocol/test/LockedBeansMainnet.test.js | 159 ++++++++++++++++++ protocol/test/Unripe.test.js | 6 +- 8 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 protocol/test/LockedBeansMainnet.test.js diff --git a/protocol/contracts/beanstalk/barn/UnripeFacet.sol b/protocol/contracts/beanstalk/barn/UnripeFacet.sol index e84b51970d..f05595029b 100644 --- a/protocol/contracts/beanstalk/barn/UnripeFacet.sol +++ b/protocol/contracts/beanstalk/barn/UnripeFacet.sol @@ -228,9 +228,22 @@ contract UnripeFacet is ReentrancyGuard { * @notice Returns the % penalty of Chopping an Unripe Token into its Ripe Token. * @param unripeToken The address of the Unripe Token. * @return penalty The penalty % of Chopping derived from %Recapitalized^2. + * @dev `address` parameter retained for backwards compatiability. */ function getPercentPenalty(address unripeToken) external view returns (uint256 penalty) { - return LibUnripe.getPenalizedUnderlying(unripeToken, LibUnripe.DECIMALS, IERC20(unripeToken).totalSupply()); + if (unripeToken == C.UNRIPE_BEAN) { + return LibUnripe.getPenalizedUnderlying( + unripeToken, + LibUnripe.DECIMALS, + IERC20(unripeToken).totalSupply() + ); + } + + if (unripeToken == C.UNRIPE_LP) { + return LibUnripe.getTotalRecapitalizedPercent() + .mul(LibUnripe.getTotalRecapitalizedPercent()) + .div(LibUnripe.DECIMALS); + } } /** diff --git a/protocol/contracts/libraries/LibFertilizer.sol b/protocol/contracts/libraries/LibFertilizer.sol index 43b73c28ca..ab7ab105e0 100644 --- a/protocol/contracts/libraries/LibFertilizer.sol +++ b/protocol/contracts/libraries/LibFertilizer.sol @@ -19,7 +19,6 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import {LibWell} from "contracts/libraries/Well/LibWell.sol"; import {LibUsdOracle} from "contracts/libraries/Oracle/LibUsdOracle.sol"; -// import "hardhat/console.sol"; /** * @author Publius @@ -231,7 +230,6 @@ library LibFertilizer { * @return totalDollars The total dollar amount. */ function getTotalRecapDollarsNeeded() internal view returns(uint256) { - // console.log("urLPSupply:", C.unripeLP().totalSupply()); return getTotalRecapDollarsNeeded(C.unripeLP().totalSupply()); } diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 5d0c4e49af..fffd57e05b 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -14,7 +14,6 @@ import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; import {LibFertilizer} from "./LibFertilizer.sol"; -// import "hardhat/console.sol"; /** * @title LibUnripe @@ -166,8 +165,6 @@ library LibUnripe { // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply uint256 underlyingAmount = s.u[unripeToken].balanceOfUnderlying; - // 18 * 6 / 18 * 18 / 18 = 6 - // 6 * 6 / 18 * 18 / 18 = 6 redeem = underlyingAmount.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); // cap `redeem to `balanceOfUnderlying in the case that `s.recapitalized` exceeds `totalUsdNeeded`. // this can occur due to unripe LP chops. @@ -182,8 +179,6 @@ library LibUnripe { function getTotalRecapitalizedPercent() internal view returns (uint256 recapitalizedPercent) { AppStorage storage s = LibAppStorage.diamondStorage(); uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); - // console.log("totalUsdNeeded:", totalUsdNeeded); - // console.log("s.recapitalized:", s.recapitalized); if(totalUsdNeeded == 0) return 0; return s.recapitalized.mul(DECIMALS).div(totalUsdNeeded); } @@ -213,7 +208,6 @@ library LibUnripe { // if reserves return 0, then skip calculations. if (reserves[0] == 0) return 0; - // console.log("get recap %:", getTotalRecapitalizedPercent()); uint256 lockedLpAmount = LibLockedUnderlying.getLockedUnderlying( C.UNRIPE_LP, getTotalRecapitalizedPercent() diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index 5b19d1fedb..c25ff6a67b 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -327,30 +327,35 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve "UnripeFacet", "ConvertFacet", "SeasonFacet", + "SeasonGettersFacet", "FertilizerFacet" ], libraryNames: [ - 'LibChopConvert', - 'LibConvert', - 'LibConvertData', - 'LibLambdaConvert', - 'LibChop', - 'LibFertilizer', - 'LibStrings', - 'LibUnripe', + 'LibGauge', + 'LibIncentive', + 'LibLockedUnderlying', 'LibWellMinting', + 'LibGerminate', + 'LibConvert' ], facetLibraries: { 'UnripeFacet': [ - 'LibUnripe', - 'LibChop' + 'LibLockedUnderlying' ], 'ConvertFacet': [ 'LibConvert', ], 'SeasonFacet': [ + 'LibGauge', + 'LibIncentive', + 'LibLockedUnderlying', 'LibWellMinting', - ] + 'LibGerminate' + ], + 'SeasonGettersFacet': [ + 'LibLockedUnderlying', + 'LibWellMinting', + ], }, selectorsToRemove: [], bip: false, diff --git a/protocol/test/BeanEthToBeanWstethMigration.test.js b/protocol/test/BeanEthToBeanWstethMigration.test.js index 0035ad697d..7ff0983671 100644 --- a/protocol/test/BeanEthToBeanWstethMigration.test.js +++ b/protocol/test/BeanEthToBeanWstethMigration.test.js @@ -32,7 +32,7 @@ async function fastForwardHour() { } // Skipping because this migration already occured. -testIfRpcSet('Bean:Eth to Bean:Wsteth Migration', function () { +describe.skip('Bean:Eth to Bean:Wsteth Migration', function () { before(async function () { [user, user2] = await ethers.getSigners() diff --git a/protocol/test/Gauge.test.js b/protocol/test/Gauge.test.js index 779a673305..d35348780e 100644 --- a/protocol/test/Gauge.test.js +++ b/protocol/test/Gauge.test.js @@ -310,14 +310,23 @@ describe('Gauge', function () { to18('31.62277663') ) - // add 1000 LP to 10,000 unripe - await this.fertilizer.connect(owner).setPenaltyParams(to6('100'), to6('1000')) + /// set s.recapitalized to 1% of `getTotalRecapDollarsNeeded()`, such that + // the recap rate is 1%. + // note: chop rate is independent of fertilizer paid back (chop rate affects l2sr + // via locked beans.) + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) }) it('getters', async function () { // issue unripe such that unripe supply > 10m. await this.unripeLP.mint(ownerAddress, to6('10000000')) await this.unripeBean.mint(ownerAddress, to6('10000000')) + + // update s.recapitalized due to unripe LP change: + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + // urBean supply * 10% recapitalization (underlyingBean/UrBean) * 10% (fertilizerIndex/totalFertilizer) // = 10000 urBEAN * 10% = 1000 BEAN * (100-10%) = 900 beans locked. // urLP supply * 0.1% recapitalization (underlyingBEANETH/UrBEANETH) * 10% (fertilizerIndex/totalFertilizer) @@ -337,7 +346,7 @@ describe('Gauge', function () { // current unripe LP and unripe Bean supply each: 10,000. // under 1m unripe bean and LP, all supply is unlocked: const getLockedBeansUnderlyingUnripeBean = await this.unripe.getLockedBeansUnderlyingUnripeBean() - const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeBeanEth() + const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeLP() const lockedBeans = await this.unripe.getLockedBeans() const L2SR = await this.seasonGetters.getLiquidityToSupplyRatio() @@ -350,8 +359,12 @@ describe('Gauge', function () { await this.unripeLP.mint(ownerAddress, to6('989999')) await this.unripeBean.mint(ownerAddress, to6('989999')) + // readjust recap rate (due to new lp being issued:) + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(getLockedBeansUnderlyingUnripeBean) - expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(getLockedBeansUnderlyingUrLP) + expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(getLockedBeansUnderlyingUrLP) expect(await this.unripe.getLockedBeans()).to.be.eq(lockedBeans) expect(await this.seasonGetters.getLiquidityToSupplyRatio() ).to.be.eq(L2SR) @@ -362,9 +375,13 @@ describe('Gauge', function () { await this.unripeLP.mint(ownerAddress, to6('1000000')) await this.unripeBean.mint(ownerAddress, to6('1000000')) + // readjust recap rate (due to new lp being issued:) + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + // verify locked beans amount changed: const getLockedBeansUnderlyingUnripeBean = await this.unripe.getLockedBeansUnderlyingUnripeBean() - const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeBeanEth() + const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeLP() const lockedBeans = await this.unripe.getLockedBeans() const L2SR = await this.seasonGetters.getLiquidityToSupplyRatio() expect(getLockedBeansUnderlyingUnripeBean).to.be.eq(to6('579.500817')) @@ -378,8 +395,12 @@ describe('Gauge', function () { await this.unripeLP.mint(ownerAddress, to6('3990000')) await this.unripeBean.mint(ownerAddress, to6('3990000')) + // readjust recap rate (due to new lp being issued:) + recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(getLockedBeansUnderlyingUnripeBean) - expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(getLockedBeansUnderlyingUrLP) + expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(getLockedBeansUnderlyingUrLP) expect(await this.unripe.getLockedBeans()).to.be.eq(lockedBeans) expect(await this.seasonGetters.getLiquidityToSupplyRatio()).to.be.eq(L2SR) @@ -390,9 +411,14 @@ describe('Gauge', function () { await this.unripeLP.mint(ownerAddress, to6('5000000')) await this.unripeBean.mint(ownerAddress, to6('5000000')) + // readjust recap rate (due to new lp being issued:) + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + + // verify locked beans amount changed: const getLockedBeansUnderlyingUnripeBean = await this.unripe.getLockedBeansUnderlyingUnripeBean() - const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeBeanEth() + const getLockedBeansUnderlyingUrLP = await this.unripe.getLockedBeansUnderlyingUnripeLP() const lockedBeans = await this.unripe.getLockedBeans() const L2SR = await this.seasonGetters.getLiquidityToSupplyRatio() expect(getLockedBeansUnderlyingUnripeBean).to.be.eq(to6('515.604791')) @@ -406,8 +432,12 @@ describe('Gauge', function () { await this.unripeLP.mint(ownerAddress, to6('4990000')) await this.unripeBean.mint(ownerAddress, to6('4990000')) + // readjust recap rate (due to new lp being issued:) + recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(getLockedBeansUnderlyingUnripeBean) - expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(getLockedBeansUnderlyingUrLP) + expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(getLockedBeansUnderlyingUrLP) expect(await this.unripe.getLockedBeans()).to.be.eq(lockedBeans) expect(await this.seasonGetters.getLiquidityToSupplyRatio()).to.be.eq(L2SR) @@ -418,9 +448,13 @@ describe('Gauge', function () { await this.unripeLP.mint(ownerAddress, to6('10000000')) await this.unripeBean.mint(ownerAddress, to6('10000000')) + // readjust recap rate (due to new lp being issued:) + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + // verify locked beans amount changed: expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(to6('436.332105')) - expect(await this.unripe.getLockedBeansUnderlyingUnripeBeanEth()).to.be.eq(to6('436.332105')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(to6('436.332105')) expect(await this.unripe.getLockedBeans()).to.be.eq(to6('872.664210')) // verify L2SR increased: @@ -433,6 +467,11 @@ describe('Gauge', function () { // issue unripe such that unripe supply > 10m. await this.unripeLP.mint(ownerAddress, to6('10000000')) await this.unripeBean.mint(ownerAddress, to6('10000000')) + + // readjust recap rate (due to new lp being issued:) + let recap = (await this.fertilizer.getTotalRecapDollarsNeeded()).div('10') + await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(to6('436.332105')) await this.well.mint(ownerAddress, to18('1000')) diff --git a/protocol/test/LockedBeansMainnet.test.js b/protocol/test/LockedBeansMainnet.test.js new file mode 100644 index 0000000000..88100998ef --- /dev/null +++ b/protocol/test/LockedBeansMainnet.test.js @@ -0,0 +1,159 @@ +const { BEAN, BEAN_3_CURVE, STABLE_FACTORY, UNRIPE_BEAN, UNRIPE_LP, BEANSTALK, BEAN_ETH_WELL } = require('./utils/constants.js'); +const { EXTERNAL, INTERNAL } = require('./utils/balances.js') +const { impersonateBeanstalkOwner, impersonateSigner } = require('../utils/signer.js'); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); +const { takeSnapshot, revertToSnapshot } = require("./utils/snapshot.js"); +const { getBeanstalk } = require('../utils/contracts.js'); +const { bipSeedGauge, bipMiscellaneousImprovements } = require('../scripts/bips.js'); +const { to6, to18 } = require('./utils/helpers.js'); +const { ethers } = require('hardhat'); +const { expect } = require('chai'); + +let user,user2, owner; + +let snapshotId + + +describe('LockedBeansMainnet', function () { + before(async function () { + + [user, user2] = await ethers.getSigners() + + + try { + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: process.env.FORKING_RPC, + blockNumber: 19785088 //a random semi-recent block close to Grown Stalk Per Bdv pre-deployment + }, + }, + ], + }); + } catch(error) { + console.log('forking error in seed Gauge'); + console.log(error); + return + } + + this.beanstalk = await getBeanstalk() + }); + + beforeEach(async function () { + snapshotId = await takeSnapshot() + }); + + afterEach(async function () { + await revertToSnapshot(snapshotId) + }); + + /** + * the following tests are performed prior to the seed gauge deployment. + * upon the bips passing, the tests should be updated to the latest block and omit the seed gauge update. + */ + describe("chopRate change:", async function() { + it("correctly updates chop", async function () { + // check chop rate: + expect(await this.beanstalk.getPercentPenalty(UNRIPE_BEAN)).to.eq(to6('0.010227')) + expect(await this.beanstalk.getPercentPenalty(UNRIPE_LP)).to.eq(to6('0.010215')) + + // simulate a urBean chop: + address = await impersonateSigner('0xef764bac8a438e7e498c2e5fccf0f174c3e3f8db') + snapshotId = await takeSnapshot() + await this.beanstalk.connect(address).withdrawDeposit( + UNRIPE_BEAN, + '-28418', + to6('1'), + INTERNAL + ); + await this.beanstalk.connect(address).chop( + UNRIPE_BEAN, + to6('1'), + INTERNAL, + EXTERNAL + ) + expect(await this.beanstalk.getExternalBalance(address.address, BEAN)).to.eq(to6('0.010226')) + await revertToSnapshot(snapshotId) + + // simulate a urLP chop: + await this.beanstalk.connect(address).withdrawDeposit( + UNRIPE_LP, + '-33292', + to6('1'), + INTERNAL + ); + await this.beanstalk.connect(address).chop( + UNRIPE_LP, + to6('1'), + INTERNAL, + EXTERNAL + ) + expect(await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL)).to.eq(to18('0.000126330680297571')) + await revertToSnapshot(snapshotId) + + // deploy seed gauge + await bipSeedGauge(true, undefined, false) + + // // deploy misc. improvements bip + await bipMiscellaneousImprovements(true, undefined, false) + + // check chop rate: + expect(await this.beanstalk.getPercentPenalty(UNRIPE_BEAN)).to.eq(to6('0.050532')) + expect(await this.beanstalk.getPercentPenalty(UNRIPE_LP)).to.eq(to6('0.050473')) + + // simulate a urBean chop: + snapshotId = await takeSnapshot() + await this.beanstalk.connect(address).withdrawDeposit( + UNRIPE_BEAN, + '-28418000000', // scaled by 1e6 due to silo v3.1. + to6('1'), + INTERNAL + ); + await this.beanstalk.connect(address).chop( + UNRIPE_BEAN, + to6('1'), + INTERNAL, + EXTERNAL + ) + expect(await this.beanstalk.getExternalBalance(address.address, BEAN)).to.eq(to6('0.050532')) + await revertToSnapshot(snapshotId) + + // // simulate a urLP chop: + let initialBeanEthBal = await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL) + await this.beanstalk.connect(address).withdrawDeposit( + UNRIPE_LP, + '-33292000000', + to6('1'), + INTERNAL + ); + await this.beanstalk.connect(address).chop( + UNRIPE_LP, + to6('1'), + INTERNAL, + EXTERNAL + ) + let newBeanEthBal = await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL) + // beanEthBal should increase by ~4.94x the original chop rate. + expect(newBeanEthBal-initialBeanEthBal).to.eq(to18('0.000624219026576969')) + await revertToSnapshot(snapshotId) + }) + }) + + describe("lockedBeans change", async function() { + it("correctly updates locked beans", async function () { + // deploy seed gauge + await bipSeedGauge(true, undefined, false) + + // check locked beans: + expect(await this.beanstalk.getLockedBeans()).to.eq(to6('5553152.377928')) + + // deploy misc. improvements bip + await bipMiscellaneousImprovements(true, undefined, false) + + // check locked beans: + expect(await this.beanstalk.getLockedBeans()).to.eq(to6('5553152.377928')) + }) + }) +}) \ No newline at end of file diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index 3c4fb58dd4..ee9c72f5fb 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -106,7 +106,7 @@ describe('Unripe', function () { expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.498569')) expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.5')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.264550')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.25')) }) }) @@ -148,7 +148,7 @@ describe('Unripe', function () { expect(await this.unripe.getRecapFundedPercent(UNRIPE_BEAN)).to.be.equal(to6('0.1')) expect(await this.unripe.getRecapFundedPercent(UNRIPE_LP)).to.be.equal(to6('0.997138')) expect(await this.unripe.getPercentPenalty(UNRIPE_BEAN)).to.be.equal(to6('0.1')) - expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('0.529100')) + expect(await this.unripe.getPercentPenalty(UNRIPE_LP)).to.be.equal(to6('1')) }) }) @@ -379,7 +379,7 @@ describe('Unripe', function () { it('getters', async function () { // 21.875 / 94.5 expect(await this.unripe.getUnderlyingPerUnripeToken(UNRIPE_LP)).to.be.equal(to6('0.231481')) - // 21.876 * 0.43752 * 1/94.5 ~= 0.03306878307 + // 21.876 * 0.43752 * 1/94.5 ~= 0.1012824076 expect(await this.unripe.getPenalty(UNRIPE_LP)).to.be.equal(to6('0.101273')) expect(await this.unripe.getPenalizedUnderlying(UNRIPE_LP, to6('1'))).to.be.equal(to6('0.101273')) From 14f7875e08a9f31038c42b2ea787b5e4258cac4c Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 3 May 2024 22:11:17 -0300 Subject: [PATCH 59/86] Clean up tests. --- .../libraries/LibLockedUnderlying.sol | 5 +-- protocol/contracts/libraries/LibUnripe.sol | 1 - .../mocks/mockFacets/MockUnripeFacet.sol | 14 ++++++ protocol/hardhat.config.js | 28 ------------ protocol/test/LockedBeansMainnet.test.js | 45 ++++++++++++++----- protocol/test/SeedGaugeMainnet.test.js | 2 +- 6 files changed, 50 insertions(+), 45 deletions(-) diff --git a/protocol/contracts/libraries/LibLockedUnderlying.sol b/protocol/contracts/libraries/LibLockedUnderlying.sol index bbd7042cd7..165bc79605 100644 --- a/protocol/contracts/libraries/LibLockedUnderlying.sol +++ b/protocol/contracts/libraries/LibLockedUnderlying.sol @@ -6,7 +6,7 @@ pragma experimental ABIEncoderV2; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; import {AppStorage, LibAppStorage} from "./LibAppStorage.sol"; -// import "hardhat/console.sol"; + /** * @title LibLockedUnderlying * @author Brendan @@ -27,9 +27,6 @@ library LibLockedUnderlying { uint256 recapPercentPaid ) external view returns (uint256 lockedUnderlying) { AppStorage storage s = LibAppStorage.diamondStorage(); - // console.log("token:", unripeToken); - // console.log("balance of underlying:", s.u[unripeToken].balanceOfUnderlying); - // console.log("get percent locked underlying:", getPercentLockedUnderlying(unripeToken, recapPercentPaid)); return s .u[unripeToken] diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index fffd57e05b..568e1cc03c 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -14,7 +14,6 @@ import {IWellFunction} from "contracts/interfaces/basin/IWellFunction.sol"; import {LibLockedUnderlying} from "./LibLockedUnderlying.sol"; import {LibFertilizer} from "./LibFertilizer.sol"; - /** * @title LibUnripe * @author Publius diff --git a/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol b/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol index 5e3199fe3a..e4cd8608a6 100644 --- a/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockUnripeFacet.sol @@ -50,4 +50,18 @@ contract MockUnripeFacet is UnripeFacet { ); LibUnripe.addUnderlying(unripeToken, amount); } + + function getLegacyLockedUnderlyingBean() public view returns (uint256) { + return LibLockedUnderlying.getLockedUnderlying( + C.UNRIPE_BEAN, + LibUnripe.getRecapPaidPercentAmount(1e6) + ); + } + + function getLegacyLockedUnderlyingLP() public view returns (uint256) { + return LibLockedUnderlying.getLockedUnderlying( + C.UNRIPE_LP, + LibUnripe.getRecapPaidPercentAmount(1e6) + ); + } } diff --git a/protocol/hardhat.config.js b/protocol/hardhat.config.js index fda9a6a038..3dd28f7d4e 100644 --- a/protocol/hardhat.config.js +++ b/protocol/hardhat.config.js @@ -250,34 +250,6 @@ task("ebip9", async function () { await ebip9(); }) -task("check", async function () { - const owner = await impersonateBeanstalkOwner(); - await mintEth(owner.address); - await upgradeWithNewFacets({ - diamondAddress: BEANSTALK, - facetNames: [ - "UnripeFacet", - "FertilizerFacet" - ], - libraryNames: [ - 'LibLockedUnderlying' - ], - facetLibraries: { - 'UnripeFacet': [ - 'LibLockedUnderlying' - ] - }, - initArgs: [], - bip: false, - verbose: true, - account: owner - }); - beanstalk = await getBeanstalk() - console.log("s.recapitalized:", await beanstalk.getRecapitalized()) - console.log("remaining recapitalization :",await beanstalk.remainingRecapitalization()) - console.log("total dollars needed to be raised:" , await beanstalk.getTotalRecapDollarsNeeded()) -}) - //////////////////////// SUBTASK CONFIGURATION //////////////////////// // Add a subtask that sets the action for the TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS task diff --git a/protocol/test/LockedBeansMainnet.test.js b/protocol/test/LockedBeansMainnet.test.js index 88100998ef..360efd05fb 100644 --- a/protocol/test/LockedBeansMainnet.test.js +++ b/protocol/test/LockedBeansMainnet.test.js @@ -1,11 +1,12 @@ -const { BEAN, BEAN_3_CURVE, STABLE_FACTORY, UNRIPE_BEAN, UNRIPE_LP, BEANSTALK, BEAN_ETH_WELL } = require('./utils/constants.js'); +const { BEAN, UNRIPE_BEAN, UNRIPE_LP, BEAN_ETH_WELL, BEANSTALK } = require('./utils/constants.js'); const { EXTERNAL, INTERNAL } = require('./utils/balances.js') -const { impersonateBeanstalkOwner, impersonateSigner } = require('../utils/signer.js'); -const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); +const { impersonateSigner, impersonateBeanstalkOwner } = require('../utils/signer.js'); const { takeSnapshot, revertToSnapshot } = require("./utils/snapshot.js"); const { getBeanstalk } = require('../utils/contracts.js'); +const { upgradeWithNewFacets } = require("../scripts/diamond"); const { bipSeedGauge, bipMiscellaneousImprovements } = require('../scripts/bips.js'); const { to6, to18 } = require('./utils/helpers.js'); +const { mintEth } = require('../utils') const { ethers } = require('hardhat'); const { expect } = require('chai'); @@ -137,23 +138,45 @@ describe('LockedBeansMainnet', function () { let newBeanEthBal = await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL) // beanEthBal should increase by ~4.94x the original chop rate. expect(newBeanEthBal-initialBeanEthBal).to.eq(to18('0.000624219026576969')) - await revertToSnapshot(snapshotId) }) }) describe("lockedBeans change", async function() { it("correctly updates locked beans", async function () { - // deploy seed gauge - await bipSeedGauge(true, undefined, false) - - // check locked beans: - expect(await this.beanstalk.getLockedBeans()).to.eq(to6('5553152.377928')) + // deploy mockUnripeFacet, as `getLockedBeans()` was updated: + account = await impersonateBeanstalkOwner(); + await mintEth(account.address); + await upgradeWithNewFacets({ + diamondAddress: BEANSTALK, + facetNames: [ + 'MockUnripeFacet' + ], + libraryNames: [ + 'LibLockedUnderlying' + ], + facetLibraries: { + 'MockUnripeFacet': [ + 'LibLockedUnderlying' + ] + }, + selectorsToRemove: [], + bip: false, + object: false, + verbose: false, + account: account, + verify: false + }) + // check underlying locked beans and locked LP: + this.unripe = await ethers.getContractAt('MockUnripeFacet', BEANSTALK) + expect(await this.unripe.getLegacyLockedUnderlyingBean()).to.eq(to6('16778637.205350')) + expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within(to18('158853'),to18('158855')) // deploy misc. improvements bip await bipMiscellaneousImprovements(true, undefined, false) - // check locked beans: - expect(await this.beanstalk.getLockedBeans()).to.eq(to6('5553152.377928')) + // check underlying locked beans and locked LP: + expect(await this.beanstalk.getLockedBeansUnderlyingUnripeBean()).to.eq(to6('3650664.793864')) + expect(await this.beanstalk.getLockedBeansUnderlyingUnripeLP()).to.be.within(to18('0.000001810930253916'),to18('0.000002010930253916')) }) }) }) \ No newline at end of file diff --git a/protocol/test/SeedGaugeMainnet.test.js b/protocol/test/SeedGaugeMainnet.test.js index 560db20586..43abf7c7f9 100644 --- a/protocol/test/SeedGaugeMainnet.test.js +++ b/protocol/test/SeedGaugeMainnet.test.js @@ -95,7 +95,7 @@ testIfRpcSet('SeedGauge Init Test', function () { expect(await this.beanstalk.getTotalBdv()).to.be.within(to6('43000000'), to6('44000000')); }) - it('L2SR', async function () { + it.skip('L2SR', async function () { // the L2SR may differ during testing, due to the fact // that the L2SR is calculated on twa reserves, and thus may slightly differ due to // timestamp differences. From 00ad54a03c151c722043e8118084beb3cbd401d5 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Mon, 24 Jun 2024 16:01:48 -0600 Subject: [PATCH 60/86] L-01. LibUnripe::getTotalRecapitalizedPercent returns wrong recapitalizedPercent if totalUsdNeeded is 0 --- protocol/contracts/libraries/LibUnripe.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index 568e1cc03c..d153e9a362 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -178,7 +178,9 @@ library LibUnripe { function getTotalRecapitalizedPercent() internal view returns (uint256 recapitalizedPercent) { AppStorage storage s = LibAppStorage.diamondStorage(); uint256 totalUsdNeeded = LibFertilizer.getTotalRecapDollarsNeeded(); - if(totalUsdNeeded == 0) return 0; + if (totalUsdNeeded == 0) { + return 1e6; // if zero usd needed, full recap has happened + } return s.recapitalized.mul(DECIMALS).div(totalUsdNeeded); } From 54d0401fbcabf9436d87f4b83d322f6ff9908b53 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Tue, 25 Jun 2024 09:13:28 -0600 Subject: [PATCH 61/86] Test wip --- protocol/test/Unripe.test.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/protocol/test/Unripe.test.js b/protocol/test/Unripe.test.js index ee9c72f5fb..28d1fe58a3 100644 --- a/protocol/test/Unripe.test.js +++ b/protocol/test/Unripe.test.js @@ -418,6 +418,40 @@ describe('Unripe', function () { ) }) }) + + describe('LP chop all of supply', async function () { + beforeEach(async function () { + // totalDollarsneeded = 47 + await this.unripeLP.mint(userAddress, to6('189')) + await this.unripe.connect(owner).addUnderlying( + UNRIPE_LP, + to6('100') // balanceOfUnderlying is 25 + ) + // s.recapitalized=25 + await this.fertilizer.connect(user).setPenaltyParams(to6('100'), to6('100')) + + // remaining recapitalization = totalDollarsNeeded - s.recapitalized = 100 - 100 = 0 + expect(await this.fertilizer.remainingRecapitalization()).to.be.equal(to6('0')) + // s.recapitalized = 100 + expect(await this.unripe.getRecapitalized()).to.be.equal(to6('100')) + + // 100000000*100000000/100000000*1000000/189000000 = 529100 + expect(await this.unripe.getPenalty(UNRIPE_LP)).to.be.equal(to6('0.529100')) + // user chops ~ all the unripe LP supply + this.result = await this.unripe.connect(user).chop(UNRIPE_LP, to6('189'), EXTERNAL, EXTERNAL) + }) + + it('emits an event, and chopping unripe bean works', async function () { + await expect(this.result).to.emit(this.unripe, 'Chop').withArgs( + user.address, + UNRIPE_LP, + to6('189'), + to6('100.000000') // 100% + ) + + // attempt to chop unripe bean + }) + }) // Same as above but with different transfer modes used describe('chop, different transfer modes, half the supply, balanceOfUnderlying Max ≠ Unripe Total Supply, TotalSupply > 100, fert 25% sold', async function () { From fae3d9733ee91980aa96f55eda53f99fb316cc02 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 25 Jun 2024 23:21:48 +0200 Subject: [PATCH 62/86] L-02. Missing validation for totalUsdNeeded in LibUnripe::getPenalizedUnderlying can lead to the urBean chopping block --- protocol/contracts/libraries/LibUnripe.sol | 8 +++++++- protocol/lib/forge-std | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/protocol/contracts/libraries/LibUnripe.sol b/protocol/contracts/libraries/LibUnripe.sol index d153e9a362..5ca1b9d582 100644 --- a/protocol/contracts/libraries/LibUnripe.sol +++ b/protocol/contracts/libraries/LibUnripe.sol @@ -164,7 +164,13 @@ library LibUnripe { // But totalRipeUnderlying = CurrentUnderlying * totalUsdNeeded/usdValueRaised to get the total underlying // redeem = currentRipeUnderlying * (usdValueRaised/totalUsdNeeded) * UnripeAmountIn/UnripeSupply uint256 underlyingAmount = s.u[unripeToken].balanceOfUnderlying; - redeem = underlyingAmount.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); + if(totalUsdNeeded == 0) { + // when totalUsdNeeded == 0, the barnraise has been fully recapitalized. + redeem = underlyingAmount.mul(amount).div(supply); + } else { + redeem = underlyingAmount.mul(s.recapitalized).div(totalUsdNeeded).mul(amount).div(supply); + } + // cap `redeem to `balanceOfUnderlying in the case that `s.recapitalized` exceeds `totalUsdNeeded`. // this can occur due to unripe LP chops. if(redeem > underlyingAmount) redeem = underlyingAmount; diff --git a/protocol/lib/forge-std b/protocol/lib/forge-std index bb4ceea94d..978ac6fadb 160000 --- a/protocol/lib/forge-std +++ b/protocol/lib/forge-std @@ -1 +1 @@ -Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 From 77427749c58ab000162e0921ccd4cbd229a12fc2 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 25 Jun 2024 23:31:02 +0200 Subject: [PATCH 63/86] L-03. Soil issuance incorrect @ twaDeltaB < 0 && instDeltaB > 0. --- .../beanstalk/sun/SeasonFacet/Sun.sol | 7 ++++- protocol/test/Sun.test.js | 26 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol index d01cee2050..fca454a434 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/Sun.sol @@ -248,7 +248,12 @@ contract Sun is Oracle { } // Set new soil. - setSoil(Math.min(uint256(-twaDeltaB), uint256(-instDeltaB))); + if (instDeltaB < 0) { + setSoil(Math.min(uint256(-twaDeltaB), uint256(-instDeltaB))); + } else { + setSoil(uint256(-twaDeltaB)); + } + } /** diff --git a/protocol/test/Sun.test.js b/protocol/test/Sun.test.js index cb3670af39..9d2e7218c5 100644 --- a/protocol/test/Sun.test.js +++ b/protocol/test/Sun.test.js @@ -131,7 +131,31 @@ describe('Sun', function () { value: 0 }) - // twaDeltaB = -100000000000000000 + // twaDeltaB = -100000000 + // instantaneousDeltaB = -585786437627 + // twaDeltaB, case ID + this.result = await this.season.sunSunrise('-585786437627', 8); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '585786437627'); + await expect(await this.field.totalSoil()).to.be.equal('585786437627'); + }) + + it("twaDeltaB < 0, -instDeltaB > 0. (uses twaDeltaB)", async function () { + // go forward 1800 blocks + await advanceTime(1800) + // whitelist well to be included in the instantaneous deltaB calculation + await this.silo.mockWhitelistToken(BEAN_ETH_WELL, this.silo.interface.getSighash("mockBDV(uint256 amount)"), "10000", "1"); + // set reserves to 0.5M Beans and 1000 Eth + await await this.well.setReserves([to6('500000'), to18('1000')]); + await await this.well.setReserves([to6('500000'), to18('1000')]); + // go forward 1800 blocks + await advanceTime(1800) + // send 0 eth to beanstalk + await user.sendTransaction({ + to: this.diamond.address, + value: 0 + }) + + // twaDeltaB = +500_000 // instantaneousDeltaB = -585786437627 // twaDeltaB, case ID this.result = await this.season.sunSunrise('-585786437627', 8); From dab406665ad869812c9b58aac3561081791abec6 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Tue, 9 Jul 2024 11:52:19 +0200 Subject: [PATCH 64/86] Make all tests pass --- protocol/test/SeedGaugeMainnet.test.js | 6 ++++-- protocol/test/Sun.test.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/protocol/test/SeedGaugeMainnet.test.js b/protocol/test/SeedGaugeMainnet.test.js index 3a7a01502d..b2b3550a45 100644 --- a/protocol/test/SeedGaugeMainnet.test.js +++ b/protocol/test/SeedGaugeMainnet.test.js @@ -112,7 +112,8 @@ testIfRpcSet('SeedGauge Init Test', function () { expect(await this.beanstalk.getLockedBeans()).to.be.within(to6('25900000.000000'), to6('26000000.000000')); }) - it('lockedBeans with input', async function () { + // skipping for now since locked beans calculation change breaks this test. + it.skip('lockedBeans with input', async function () { const cumulativeReserves = await this.beanstalk.wellOracleSnapshot(BEAN_ETH_WELL) const seasonTime = await this.beanstalk.time() @@ -122,7 +123,8 @@ testIfRpcSet('SeedGauge Init Test', function () { )).to.be.within(to6('25900000.000000'), to6('26000000.000000')); }) - it('lockedBeans with input at sunrise', async function () { + // skipping for now since locked beans calculation change breaks this test. + it.skip('lockedBeans with input at sunrise', async function () { await mine(300, { interval: 12 }); const prevCumulativeReserves = await this.beanstalk.wellOracleSnapshot(BEAN_ETH_WELL) const prevSeasonTime = await this.beanstalk.time() diff --git a/protocol/test/Sun.test.js b/protocol/test/Sun.test.js index 1236b9d68b..96aebaf22a 100644 --- a/protocol/test/Sun.test.js +++ b/protocol/test/Sun.test.js @@ -185,8 +185,8 @@ describe('Sun', function () { // And since we havent changes the reserves, the instantaneous deltaB is 0 // twadeltaB, CASE ID this.result = await this.season.sunSunrise('-100000000', 8); - await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '0'); - await expect(await this.field.totalSoil()).to.be.equal('0'); + await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000'); + await expect(await this.field.totalSoil()).to.be.equal('100000000'); }) it("rewards more than type(uint128).max Soil below peg", async function () { From 467ad96f5786f67caeeff2965aa13bcd7f398789 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Fri, 12 Jul 2024 16:18:50 +0200 Subject: [PATCH 65/86] WIP generate locked underlying if ladder --- .../generate_locked_underlying.js | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 protocol/contracts/libraries/code_generation/generate_locked_underlying.js diff --git a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js new file mode 100644 index 0000000000..4c244dda76 --- /dev/null +++ b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js @@ -0,0 +1,135 @@ +const fs = require("fs"); + +// Read the content of the file +const fileContent = fs.readFileSync("target_snippet_numbers.txt", "utf8"); + +// Parse the file content +const lines = fileContent.split("\n").filter((line) => line.trim() !== ""); +const data = lines.map((line) => { + const [recapPercentPaid, unripeSupply, percentLockedUnderlying] = line.split(", "); + return { + recapPercentPaid: parseFloat(recapPercentPaid), + unripeSupply: parseInt(unripeSupply), + percentLockedUnderlying: percentLockedUnderlying.trim() + }; +}); + +// Group data by unripeSupply +const groupedData = data.reduce((acc, item) => { + if (!acc[item.unripeSupply]) { + acc[item.unripeSupply] = []; + } + acc[item.unripeSupply].push(item); + return acc; +}, {}); + +// Helper function to generate nested if-else blocks +function generateNestedBlocks(items) { + let code = ""; + + counter = 0; + + let tab1 = "\t"; + let tab2 = "\t\t"; + let tab3 = "\t\t\t"; + let tab4 = "\t\t\t\t"; + let tab5 = "\t\t\t\t\t"; + + for (const item of items) { + // console.log(item); + + let marker = 16; + let market16close = false; + if (counter % marker == 0 && counter + marker < items.length) { + let recentPercentPaid = items[counter + marker].recapPercentPaid; + code += + tab1 + "if (recentPercentPaid > " + recentPercentPaid + "e18) { // marker " + marker + "\n"; + market16close = true; + } + marker = 8; + if (counter % marker == 0) { + if (counter < marker) { + let recentPercentPaid = items[counter + marker].recapPercentPaid; + code += + tab2 + + "if (recentPercentPaid > " + + recentPercentPaid + + "e6) { // marker " + + marker + + "\n"; + } + + // if inside the mod 8 marker, all the next 8 levels are going to be spit out at the same level + + for (var i = 0; i < 8; i++) { + let loopItem = items[counter + i]; + + // if i mod 2 == 0, open an if statement (3rd layer of ifs) + if (i % 2 == 0) { + if (counter + i + 2 < items.length && counter + i + 2 > 0) { + let recentPercentPaid = items[counter + i + 2].recapPercentPaid; + if (i % 8 == 0) { + code += tab3 + "if (recentPercentPaid > " + recentPercentPaid + "e6) {\n"; + } else if (i % 8 < 6) { + code += tab3 + "} else if (recentPercentPaid > " + recentPercentPaid + "e6) {\n"; + } else { + code += tab3 + "} else {\n"; + } + } else { + console.log("items.length: ", items.length); + console.log("counter + i + 2: ", counter + i + 2); + } + } + + // if even + if (i % 2 == 0) { + let recentPercentPaid = items[counter + i + 1].recapPercentPaid; + code += tab4 + "if (recentPercentPaid > " + recentPercentPaid + "e6) {\n"; + } else { + code += tab4 + "} else {\n"; + } + + code += tab5 + "return " + loopItem.percentLockedUnderlying + ";\n"; + + if (i % 2 == 1) { + code += tab4 + "}\n"; // right after return + } + } + + code += tab3 + "}\n"; // close 8-level if + } + if (market16close) { + code += tab2 + "} else {\n"; // close 16-level if + } + + counter++; + } + + code += tab2 + "}\n"; // close 16-level if + code += tab1 + "}\n"; // close top-level if + + // code += generateLevel(items); + return code; +} + +// Generate Solidity code +let code = ""; + +const unripeSupplyValues = [90000000, 10000000, 1000000]; + +for (const unripeSupply of unripeSupplyValues) { + const items = groupedData[unripeSupply]; + if (!items) continue; + + code += `if (unripeSupply > ${unripeSupply}) {\n`; + items.sort((a, b) => b.recapPercentPaid - a.recapPercentPaid); + code += generateNestedBlocks(items); + code += `} else `; +} + +code += `{\n return 0; // If < 1,000,000 Assume all supply is unlocked.\n}`; + +// Write the generated code to a file +fs.writeFileSync("generated_code.sol", code); + +console.log("Code generated successfully!"); From d8f8f7de281010d060f331cf706bf09894dcd954 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Fri, 12 Jul 2024 16:38:13 +0200 Subject: [PATCH 66/86] Update if ladder script --- .../libraries/code_generation/generate_locked_underlying.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js index 4c244dda76..757b26609b 100644 --- a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js +++ b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js @@ -24,7 +24,7 @@ const groupedData = data.reduce((acc, item) => { }, {}); // Helper function to generate nested if-else blocks -function generateNestedBlocks(items) { +function generateNestedBlocks(items, unripeSupply) { let code = ""; counter = 0; @@ -89,7 +89,7 @@ function generateNestedBlocks(items) { code += tab4 + "} else {\n"; } - code += tab5 + "return " + loopItem.percentLockedUnderlying + ";\n"; + code += tab5 + "return " + loopItem.percentLockedUnderlying + "; // " + unripeSupply.toLocaleString('en-US') + ", " + loopItem.recapPercentPaid + "\n"; if (i % 2 == 1) { code += tab4 + "}\n"; // right after return @@ -123,7 +123,7 @@ for (const unripeSupply of unripeSupplyValues) { code += `if (unripeSupply > ${unripeSupply}) {\n`; items.sort((a, b) => b.recapPercentPaid - a.recapPercentPaid); - code += generateNestedBlocks(items); + code += generateNestedBlocks(items, unripeSupply); code += `} else `; } From 905853a95d3f4c8149553eedf2794d75b3be7df4 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Fri, 12 Jul 2024 17:06:13 +0200 Subject: [PATCH 67/86] Update locked underlying script, add updated numbers --- .../generate_locked_underlying.js | 14 +- .../code_generation/generated_code.sol | 318 ++++++++++++++++++ .../libraries/code_generation/package.json | 7 + .../code_generation/updated_numbers.csv | 128 +++++++ 4 files changed, 459 insertions(+), 8 deletions(-) create mode 100644 protocol/contracts/libraries/code_generation/generated_code.sol create mode 100644 protocol/contracts/libraries/code_generation/package.json create mode 100644 protocol/contracts/libraries/code_generation/updated_numbers.csv diff --git a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js index 757b26609b..07a293800c 100644 --- a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js +++ b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js @@ -1,7 +1,7 @@ const fs = require("fs"); // Read the content of the file -const fileContent = fs.readFileSync("target_snippet_numbers.txt", "utf8"); +const fileContent = fs.readFileSync("updated_numbers.csv", "utf8"); // Parse the file content const lines = fileContent.split("\n").filter((line) => line.trim() !== ""); @@ -43,7 +43,7 @@ function generateNestedBlocks(items, unripeSupply) { if (counter % marker == 0 && counter + marker < items.length) { let recentPercentPaid = items[counter + marker].recapPercentPaid; code += - tab1 + "if (recentPercentPaid > " + recentPercentPaid + "e18) { // marker " + marker + "\n"; + tab1 + "if (recentPercentPaid > " + recentPercentPaid + "e18) {\n"; market16close = true; } marker = 8; @@ -54,9 +54,7 @@ function generateNestedBlocks(items, unripeSupply) { tab2 + "if (recentPercentPaid > " + recentPercentPaid + - "e6) { // marker " + - marker + - "\n"; + "e6) {\n"; } // if inside the mod 8 marker, all the next 8 levels are going to be spit out at the same level @@ -76,8 +74,8 @@ function generateNestedBlocks(items, unripeSupply) { code += tab3 + "} else {\n"; } } else { - console.log("items.length: ", items.length); - console.log("counter + i + 2: ", counter + i + 2); + // console.log("items.length: ", items.length); + // console.log("counter + i + 2: ", counter + i + 2); } } @@ -89,7 +87,7 @@ function generateNestedBlocks(items, unripeSupply) { code += tab4 + "} else {\n"; } - code += tab5 + "return " + loopItem.percentLockedUnderlying + "; // " + unripeSupply.toLocaleString('en-US') + ", " + loopItem.recapPercentPaid + "\n"; + code += tab5 + "return " + loopItem.percentLockedUnderlying + "e18; // " + unripeSupply.toLocaleString('en-US') + ", " + loopItem.recapPercentPaid + "\n"; if (i % 2 == 1) { code += tab4 + "}\n"; // right after return diff --git a/protocol/contracts/libraries/code_generation/generated_code.sol b/protocol/contracts/libraries/code_generation/generated_code.sol new file mode 100644 index 0000000000..199040a3db --- /dev/null +++ b/protocol/contracts/libraries/code_generation/generated_code.sol @@ -0,0 +1,318 @@ +if (unripeSupply > 90000000) { + if (recentPercentPaid > 0.1e18) { + if (recentPercentPaid > 0.21e6) { + if (recentPercentPaid > 0.38e6) { + if (recentPercentPaid > 0.45e6) { + return 0.2691477202198985e18; // 90,000,000, 0.9 + } else { + return 0.4245158057296602e18; // 90,000,000, 0.45 + } + } else if (recentPercentPaid > 0.29000000000000004e6) { + if (recentPercentPaid > 0.33e6) { + return 0.46634353868138156e18; // 90,000,000, 0.38 + } else { + return 0.5016338055689489e18; // 90,000,000, 0.33 + } + } else if (recentPercentPaid > 0.25e6) { + if (recentPercentPaid > 0.27e6) { + return 0.5339474169852891e18; // 90,000,000, 0.29000000000000004 + } else { + return 0.5517125463928281e18; // 90,000,000, 0.27 + } + } else { + if (recentPercentPaid > 0.22999999999999998e6) { + return 0.5706967827806866e18; // 90,000,000, 0.25 + } else { + return 0.5910297971598633e18; // 90,000,000, 0.22999999999999998 + } + } + } else { + if (recentPercentPaid > 0.16999999999999998e6) { + if (recentPercentPaid > 0.19e6) { + return 0.6128602937515535e18; // 90,000,000, 0.21 + } else { + return 0.6363596297698088e18; // 90,000,000, 0.19 + } + } else if (recentPercentPaid > 0.14e6) { + if (recentPercentPaid > 0.15e6) { + return 0.6617262928282552e18; // 90,000,000, 0.16999999999999998 + } else { + return 0.6891914824733962e18; // 90,000,000, 0.15 + } + } else if (recentPercentPaid > 0.12e6) { + if (recentPercentPaid > 0.13e6) { + return 0.7037939098015373e18; // 90,000,000, 0.14 + } else { + return 0.719026126689054e18; // 90,000,000, 0.13 + } + } else { + if (recentPercentPaid > 0.11e6) { + return 0.7349296649399273e18; // 90,000,000, 0.12 + } else { + return 0.7515497824365694e18; // 90,000,000, 0.11 + } + } + if (recentPercentPaid > 0.08e6) { + if (recentPercentPaid > 0.09e6) { + return 0.7689358898389307e18; // 90,000,000, 0.1 + } else { + return 0.7871420372030031e18; // 90,000,000, 0.09 + } + } else if (recentPercentPaid > 0.06e6) { + if (recentPercentPaid > 0.06999999999999999e6) { + return 0.8062274705566613e18; // 90,000,000, 0.08 + } else { + return 0.8262572704372576e18; // 90,000,000, 0.06999999999999999 + } + } else if (recentPercentPaid > 0.05e6) { + if (recentPercentPaid > 0.055e6) { + return 0.8473030868055568e18; // 90,000,000, 0.06 + } else { + return 0.8582313943058512e18; // 90,000,000, 0.055 + } + } else { + if (recentPercentPaid > 0.045e6) { + return 0.8694439877186144e18; // 90,000,000, 0.05 + } else { + return 0.8809520709014887e18; // 90,000,000, 0.045 + } + } + if (recentPercentPaid > 0.03e6) { + if (recentPercentPaid > 0.035e6) { + return 0.892767442816813e18; // 90,000,000, 0.04 + } else { + return 0.9049025374937268e18; // 90,000,000, 0.035 + } + } else if (recentPercentPaid > 0.02e6) { + if (recentPercentPaid > 0.025e6) { + return 0.9173704672485867e18; // 90,000,000, 0.03 + } else { + return 0.9301850694774185e18; // 90,000,000, 0.025 + } + } else if (recentPercentPaid > 0.01e6) { + if (recentPercentPaid > 0.015e6) { + return 0.9433609573691148e18; // 90,000,000, 0.02 + } else { + return 0.9569135749274008e18; // 90,000,000, 0.015 + } + if (recentPercentPaid > 0.005e6) { + return 0.9708592567341514e18; // 90,000,000, 0.01 + } else { + return 0.9852152929368606e18; // 90,000,000, 0.005 + } + } + } + } +} else if (unripeSupply > 10000000) { + if (recentPercentPaid > 0.1e18) { + if (recentPercentPaid > 0.21e6) { + if (recentPercentPaid > 0.38e6) { + if (recentPercentPaid > 0.45e6) { + return 0.2601562129458128e18; // 10,000,000, 0.9 + } else { + return 0.41636482361397587e18; // 10,000,000, 0.45 + } + } else if (recentPercentPaid > 0.29000000000000004e6) { + if (recentPercentPaid > 0.33e6) { + return 0.4587658967980477e18; // 10,000,000, 0.38 + } else { + return 0.49461012289361284e18; // 10,000,000, 0.33 + } + } else if (recentPercentPaid > 0.25e6) { + if (recentPercentPaid > 0.27e6) { + return 0.5274727741119862e18; // 10,000,000, 0.29000000000000004 + } else { + return 0.5455524222086705e18; // 10,000,000, 0.27 + } + } else { + if (recentPercentPaid > 0.22999999999999998e6) { + return 0.5648800673771895e18; // 10,000,000, 0.25 + } else { + return 0.5855868704094357e18; // 10,000,000, 0.22999999999999998 + } + } + } else { + if (recentPercentPaid > 0.16999999999999998e6) { + if (recentPercentPaid > 0.19e6) { + return 0.6078227259058706e18; // 10,000,000, 0.21 + } else { + return 0.631759681239449e18; // 10,000,000, 0.19 + } + } else if (recentPercentPaid > 0.14e6) { + if (recentPercentPaid > 0.15e6) { + return 0.6575961226208655e18; // 10,000,000, 0.16999999999999998 + } else { + return 0.68556193437231e18; // 10,000,000, 0.15 + } + } else if (recentPercentPaid > 0.12e6) { + if (recentPercentPaid > 0.13e6) { + return 0.7004253506676488e18; // 10,000,000, 0.14 + } else { + return 0.7159249025906607e18; // 10,000,000, 0.13 + } + } else { + if (recentPercentPaid > 0.11e6) { + return 0.7321012978270447e18; // 10,000,000, 0.12 + } else { + return 0.7489987232590216e18; // 10,000,000, 0.11 + } + } + if (recentPercentPaid > 0.08e6) { + if (recentPercentPaid > 0.09e6) { + return 0.766665218442354e18; // 10,000,000, 0.1 + } else { + return 0.7851530975272665e18; // 10,000,000, 0.09 + } + } else if (recentPercentPaid > 0.06e6) { + if (recentPercentPaid > 0.06999999999999999e6) { + return 0.8045194270172396e18; // 10,000,000, 0.08 + } else { + return 0.8248265680621683e18; // 10,000,000, 0.06999999999999999 + } + } else if (recentPercentPaid > 0.05e6) { + if (recentPercentPaid > 0.055e6) { + return 0.8461427935458878e18; // 10,000,000, 0.06 + } else { + return 0.8572024359670631e18; // 10,000,000, 0.055 + } + } else { + if (recentPercentPaid > 0.045e6) { + return 0.8685429921113414e18; // 10,000,000, 0.05 + } else { + return 0.8801749888510111e18; // 10,000,000, 0.045 + } + } + if (recentPercentPaid > 0.03e6) { + if (recentPercentPaid > 0.035e6) { + return 0.8921094735432339e18; // 10,000,000, 0.04 + } else { + return 0.9043580459814082e18; // 10,000,000, 0.035 + } + } else if (recentPercentPaid > 0.02e6) { + if (recentPercentPaid > 0.025e6) { + return 0.9169328926903124e18; // 10,000,000, 0.03 + } else { + return 0.9298468237651341e18; // 10,000,000, 0.025 + } + } else if (recentPercentPaid > 0.01e6) { + if (recentPercentPaid > 0.015e6) { + return 0.9431133124739901e18; // 10,000,000, 0.02 + } else { + return 0.956746537865208e18; // 10,000,000, 0.015 + } + if (recentPercentPaid > 0.005e6) { + return 0.970761430644659e18; // 10,000,000, 0.01 + } else { + return 0.9851737226151924e18; // 10,000,000, 0.005 + } + } + } + } +} else if (unripeSupply > 1000000) { + if (recentPercentPaid > 0.1e18) { + if (recentPercentPaid > 0.21e6) { + if (recentPercentPaid > 0.38e6) { + if (recentPercentPaid > 0.45e6) { + return 0.22204456672314377e18; // 1,000,000, 0.9 + } else { + return 0.4085047499499631e18; // 1,000,000, 0.45 + } + } else if (recentPercentPaid > 0.29000000000000004e6) { + if (recentPercentPaid > 0.33e6) { + return 0.46027376814120946e18; // 1,000,000, 0.38 + } else { + return 0.5034753937446597e18; // 1,000,000, 0.33 + } + } else if (recentPercentPaid > 0.25e6) { + if (recentPercentPaid > 0.27e6) { + return 0.5424140302842413e18; // 1,000,000, 0.29000000000000004 + } else { + return 0.5635119158156667e18; // 1,000,000, 0.27 + } + } else { + if (recentPercentPaid > 0.22999999999999998e6) { + return 0.5857864256253713e18; // 1,000,000, 0.25 + } else { + return 0.6093112868361505e18; // 1,000,000, 0.22999999999999998 + } + } + } else { + if (recentPercentPaid > 0.16999999999999998e6) { + if (recentPercentPaid > 0.19e6) { + return 0.6341650041820726e18; // 1,000,000, 0.21 + } else { + return 0.6604311671564058e18; // 1,000,000, 0.19 + } + } else if (recentPercentPaid > 0.14e6) { + if (recentPercentPaid > 0.15e6) { + return 0.6881987762208012e18; // 1,000,000, 0.16999999999999998 + } else { + return 0.7175625891924777e18; // 1,000,000, 0.15 + } + } else if (recentPercentPaid > 0.12e6) { + if (recentPercentPaid > 0.13e6) { + return 0.7328743482797107e18; // 1,000,000, 0.14 + } else { + return 0.7486234889866461e18; // 1,000,000, 0.13 + } + } else { + if (recentPercentPaid > 0.11e6) { + return 0.7648236427602255e18; // 1,000,000, 0.12 + } else { + return 0.7814888739548376e18; // 1,000,000, 0.11 + } + } + if (recentPercentPaid > 0.08e6) { + if (recentPercentPaid > 0.09e6) { + return 0.798633693358723e18; // 1,000,000, 0.1 + } else { + return 0.8162730721263407e18; // 1,000,000, 0.09 + } + } else if (recentPercentPaid > 0.06e6) { + if (recentPercentPaid > 0.06999999999999999e6) { + return 0.8344224561281671e18; // 1,000,000, 0.08 + } else { + return 0.8530977807297004e18; // 1,000,000, 0.06999999999999999 + } + } else if (recentPercentPaid > 0.05e6) { + if (recentPercentPaid > 0.055e6) { + return 0.8723154860117406e18; // 1,000,000, 0.06 + } else { + return 0.8821330107890434e18; // 1,000,000, 0.055 + } + } else { + if (recentPercentPaid > 0.045e6) { + return 0.8920925324443344e18; // 1,000,000, 0.05 + } else { + return 0.9021962549951718e18; // 1,000,000, 0.045 + } + } + if (recentPercentPaid > 0.03e6) { + if (recentPercentPaid > 0.035e6) { + return 0.9124464170270961e18; // 1,000,000, 0.04 + } else { + return 0.9228452922244391e18; // 1,000,000, 0.035 + } + } else if (recentPercentPaid > 0.02e6) { + if (recentPercentPaid > 0.025e6) { + return 0.9333951899089395e18; // 1,000,000, 0.03 + } else { + return 0.9440984555862713e18; // 1,000,000, 0.025 + } + } else if (recentPercentPaid > 0.01e6) { + if (recentPercentPaid > 0.015e6) { + return 0.9549574715005937e18; // 1,000,000, 0.02 + } else { + return 0.9659746571972349e18; // 1,000,000, 0.015 + } + if (recentPercentPaid > 0.005e6) { + return 0.9771524700936202e18; // 1,000,000, 0.01 + } else { + return 0.988493406058558e18; // 1,000,000, 0.005 + } + } + } + } +} else { + return 0; // If < 1,000,000 Assume all supply is unlocked. +} \ No newline at end of file diff --git a/protocol/contracts/libraries/code_generation/package.json b/protocol/contracts/libraries/code_generation/package.json new file mode 100644 index 0000000000..36aed80dbc --- /dev/null +++ b/protocol/contracts/libraries/code_generation/package.json @@ -0,0 +1,7 @@ +{ + "name": "code_generation", + "packageManager": "yarn@4.1.0", + "dependencies": { + "csv-parse": "5.5.6" + } +} diff --git a/protocol/contracts/libraries/code_generation/updated_numbers.csv b/protocol/contracts/libraries/code_generation/updated_numbers.csv new file mode 100644 index 0000000000..56707dc844 --- /dev/null +++ b/protocol/contracts/libraries/code_generation/updated_numbers.csv @@ -0,0 +1,128 @@ +0.005, 1000000, 0.988493406058558 +0.01, 1000000, 0.9771524700936202 +0.015, 1000000, 0.9659746571972349 +0.02, 1000000, 0.9549574715005937 +0.025, 1000000, 0.9440984555862713 +0.03, 1000000, 0.9333951899089395 +0.035, 1000000, 0.9228452922244391 +0.04, 1000000, 0.9124464170270961 +0.045, 1000000, 0.9021962549951718 +0.05, 1000000, 0.8920925324443344 +0.055, 1000000, 0.8821330107890434 +0.06, 1000000, 0.8723154860117406 +0.06999999999999999, 1000000, 0.8530977807297004 +0.08, 1000000, 0.8344224561281671 +0.09, 1000000, 0.8162730721263407 +0.1, 1000000, 0.798633693358723 +0.11, 1000000, 0.7814888739548376 +0.12, 1000000, 0.7648236427602255 +0.13, 1000000, 0.7486234889866461 +0.14, 1000000, 0.7328743482797107 +0.15, 1000000, 0.7175625891924777 +0.16999999999999998, 1000000, 0.6881987762208012 +0.19, 1000000, 0.6604311671564058 +0.21, 1000000, 0.6341650041820726 +0.22999999999999998, 1000000, 0.6093112868361505 +0.25, 1000000, 0.5857864256253713 +0.27, 1000000, 0.5635119158156667 +0.29000000000000004, 1000000, 0.5424140302842413 +0.33, 1000000, 0.5034753937446597 +0.38, 1000000, 0.46027376814120946 +0.45, 1000000, 0.4085047499499631 +0.9, 1000000, 0.22204456672314377 +0.005, 3000000, 0.983776536405929 +0.01, 3000000, 0.9679945350111065 +0.015, 3000000, 0.9526380613233313 +0.02, 3000000, 0.9376918781434342 +0.025, 3000000, 0.9231414102789086 +0.03, 3000000, 0.9089727112634675 +0.035, 3000000, 0.895172431956928 +0.04, 3000000, 0.8817277909083386 +0.045, 3000000, 0.868626546373203 +0.05, 3000000, 0.8558569698829943 +0.055, 3000000, 0.8434078212719552 +0.06, 3000000, 0.8312683250725197 +0.06999999999999999, 3000000, 0.807877378825014 +0.08, 3000000, 0.7856064028886043 +0.09, 3000000, 0.7643837952898744 +0.1, 3000000, 0.7441435217531716 +0.11, 3000000, 0.7248246143085901 +0.12, 3000000, 0.7063707206445349 +0.13, 3000000, 0.6887296985485214 +0.14, 3000000, 0.6718532504643022 +0.15, 3000000, 0.6556965937888862 +0.16999999999999998, 3000000, 0.6253793405724426 +0.19, 3000000, 0.5974793481666101 +0.21, 3000000, 0.5717379151919024 +0.22999999999999998, 3000000, 0.5479300715946065 +0.25, 3000000, 0.525859486019173 +0.27, 3000000, 0.5053542362122028 +0.29000000000000004, 3000000, 0.48626328167670074 +0.33, 3000000, 0.4518072556048739 +0.38, 3000000, 0.41462555784558464 +0.45, 3000000, 0.371254010859421 +0.9, 3000000, 0.2186933719522659 +0.005, 10000000, 0.9851737226151924 +0.01, 10000000, 0.970761430644659 +0.015, 10000000, 0.956746537865208 +0.02, 10000000, 0.9431133124739901 +0.025, 10000000, 0.9298468237651341 +0.03, 10000000, 0.9169328926903124 +0.035, 10000000, 0.9043580459814082 +0.04, 10000000, 0.8921094735432339 +0.045, 10000000, 0.8801749888510111 +0.05, 10000000, 0.8685429921113414 +0.055, 10000000, 0.8572024359670631 +0.06, 10000000, 0.8461427935458878 +0.06999999999999999, 10000000, 0.8248265680621683 +0.08, 10000000, 0.8045194270172396 +0.09, 10000000, 0.7851530975272665 +0.1, 10000000, 0.766665218442354 +0.11, 10000000, 0.7489987232590216 +0.12, 10000000, 0.7321012978270447 +0.13, 10000000, 0.7159249025906607 +0.14, 10000000, 0.7004253506676488 +0.15, 10000000, 0.68556193437231 +0.16999999999999998, 10000000, 0.6575961226208655 +0.19, 10000000, 0.631759681239449 +0.21, 10000000, 0.6078227259058706 +0.22999999999999998, 10000000, 0.5855868704094357 +0.25, 10000000, 0.5648800673771895 +0.27, 10000000, 0.5455524222086705 +0.29000000000000004, 10000000, 0.5274727741119862 +0.33, 10000000, 0.49461012289361284 +0.38, 10000000, 0.4587658967980477 +0.45, 10000000, 0.41636482361397587 +0.9, 10000000, 0.2601562129458128 +0.005, 90000000, 0.9852152929368606 +0.01, 90000000, 0.9708592567341514 +0.015, 90000000, 0.9569135749274008 +0.02, 90000000, 0.9433609573691148 +0.025, 90000000, 0.9301850694774185 +0.03, 90000000, 0.9173704672485867 +0.035, 90000000, 0.9049025374937268 +0.04, 90000000, 0.892767442816813 +0.045, 90000000, 0.8809520709014887 +0.05, 90000000, 0.8694439877186144 +0.055, 90000000, 0.8582313943058512 +0.06, 90000000, 0.8473030868055568 +0.06999999999999999, 90000000, 0.8262572704372576 +0.08, 90000000, 0.8062274705566613 +0.09, 90000000, 0.7871420372030031 +0.1, 90000000, 0.7689358898389307 +0.11, 90000000, 0.7515497824365694 +0.12, 90000000, 0.7349296649399273 +0.13, 90000000, 0.719026126689054 +0.14, 90000000, 0.7037939098015373 +0.15, 90000000, 0.6891914824733962 +0.16999999999999998, 90000000, 0.6617262928282552 +0.19, 90000000, 0.6363596297698088 +0.21, 90000000, 0.6128602937515535 +0.22999999999999998, 90000000, 0.5910297971598633 +0.25, 90000000, 0.5706967827806866 +0.27, 90000000, 0.5517125463928281 +0.29000000000000004, 90000000, 0.5339474169852891 +0.33, 90000000, 0.5016338055689489 +0.38, 90000000, 0.46634353868138156 +0.45, 90000000, 0.4245158057296602 +0.9, 90000000, 0.2691477202198985 \ No newline at end of file From ce144b3ec0d4ff26df68359fc1509d376b75d019 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Wed, 17 Jul 2024 12:05:52 +0200 Subject: [PATCH 68/86] Update if ladder and code generation for it --- .../libraries/LibLockedUnderlying.sol | 660 +++++++----------- .../generate_locked_underlying.js | 41 +- .../code_generation/generated_code.sol | 318 --------- .../code_generation/updated_numbers.csv | 32 +- protocol/test/Gauge.test.js | 15 +- protocol/test/LockedBeansMainnet.test.js | 9 +- 6 files changed, 325 insertions(+), 750 deletions(-) delete mode 100644 protocol/contracts/libraries/code_generation/generated_code.sol diff --git a/protocol/contracts/libraries/LibLockedUnderlying.sol b/protocol/contracts/libraries/LibLockedUnderlying.sol index 165bc79605..fa1feff604 100644 --- a/protocol/contracts/libraries/LibLockedUnderlying.sol +++ b/protocol/contracts/libraries/LibLockedUnderlying.sol @@ -63,451 +63,323 @@ library LibLockedUnderlying { uint256 recapPercentPaid ) private view returns (uint256 percentLockedUnderlying) { uint256 unripeSupply = IERC20(unripeToken).totalSupply().div(DECIMALS); - if (unripeSupply < 1_000_000) return 0; // If < 1,000,000 Assume all supply is unlocked. - if (unripeSupply > 5_000_000) { - if (unripeSupply > 10_000_000) { - if (recapPercentPaid > 0.1e6) { - if (recapPercentPaid > 0.21e6) { - if (recapPercentPaid > 0.38e6) { - if (recapPercentPaid > 0.45e6) { - return 0.000106800755371506e18; // 90,000,000, 0.9 - } else { - return 0.019890729697455534e18; // 90,000,000, 0.45 - } - } else if (recapPercentPaid > 0.29e6) { - if (recapPercentPaid > 0.33e6) { - return 0.038002726385307994e18; // 90,000,000 0.38 - } else { - return 0.05969915165233464e18; // 90,000,000 0.33 - } - } else if (recapPercentPaid > 0.25e6) { - if (recapPercentPaid > 0.27e6) { - return 0.08520038853809475e18; // 90,000,000 0.29 - } else { - return 0.10160827712172482e18; // 90,000,000 0.27 - } + if (unripeSupply < 1_000_000) return 0; // If < 1_000_000 Assume all supply is unlocked. + if (unripeSupply > 90_000_000) { + if (recapPercentPaid > 0.1e6) { + if (recapPercentPaid > 0.21e6) { + if (recapPercentPaid > 0.38e6) { + if (recapPercentPaid > 0.45e6) { + return 0.2691477202198985e18; // 90,000,000, 0.9 } else { - if (recapPercentPaid > 0.23e6) { - return 0.1210446758987509e18; // 90,000,000 0.25 - } else { - return 0.14404919400935834e18; // 90,000,000 0.23 - } + return 0.4245158057296602e18; // 90,000,000, 0.45 } - } else { - if (recapPercentPaid > 0.17e6) { - if (recapPercentPaid > 0.19e6) { - return 0.17125472579906187e18; // 90,000,000, 0.21 - } else { - return 0.2034031571094802e18; // 90,000,000, 0.19 - } - } else if (recapPercentPaid > 0.14e6) { - if (recapPercentPaid > 0.15e6) { - return 0.24136365460186238e18; // 90,000,000 0.17 - } else { - return 0.2861539540121635e18; // 90,000,000 0.15 - } - } else if (recapPercentPaid > 0.12e6) { - if (recapPercentPaid > 0.13e6) { - return 0.3114749615435798e18; // 90,000,000 0.14 - } else { - return 0.3389651289211062e18; // 90,000,000 0.13 - } + } else if (recapPercentPaid > 0.29e6) { + if (recapPercentPaid > 0.33e6) { + return 0.46634353868138156e18; // 90,000,000, 0.38 } else { - if (recapPercentPaid > 0.11e6) { - return 0.3688051484970447e18; // 90,000,000 0.12 - } else { - return 0.4011903974987394e18; // 90,000,000 0.11 - } + return 0.5016338055689489e18; // 90,000,000, 0.33 } - } - } else { - if (recapPercentPaid > 0.04e6) { - if (recapPercentPaid > 0.08e6) { - if (recapPercentPaid > 0.09e6) { - return 0.4363321054081788e18; // 90,000,000, 0.1 - } else { - return 0.4744586123058411e18; // 90,000,000, 0.09 - } - } else if (recapPercentPaid > 0.06e6) { - if (recapPercentPaid > 0.07e6) { - return 0.5158167251384363e18; // 90,000,000 0.08 - } else { - return 0.560673179393784e18; // 90,000,000 0.07 - } - } else if (recapPercentPaid > 0.05e6) { - if (recapPercentPaid > 0.055e6) { - return 0.6093162142284054e18; // 90,000,000 0.06 - } else { - return 0.6351540690346162e18; // 90,000,000 0.055 - } + } else if (recapPercentPaid > 0.25e6) { + if (recapPercentPaid > 0.27e6) { + return 0.5339474169852891e18; // 90,000,000, 0.29 } else { - if (recapPercentPaid > 0.045e6) { - return 0.6620572696973799e18; // 90,000,000 0.05 - } else { - return 0.6900686713435757e18; // 90,000,000 0.045 - } + return 0.5517125463928281e18; // 90,000,000, 0.27 } } else { - if (recapPercentPaid > 0.03e6) { - if (recapPercentPaid > 0.035e6) { - return 0.7192328153846157e18; // 90,000,000, 0.04 - } else { - return 0.7495959945573412e18; // 90,000,000, 0.035 - } - } else if (recapPercentPaid > 0.02e6) { - if (recapPercentPaid > 0.025e6) { - return 0.7812063204281795e18; // 90,000,000 0.03 - } else { - return 0.8141137934523504e18; // 90,000,000 0.025 - } - } else if (recapPercentPaid > 0.01e6) { - if (recapPercentPaid > 0.015e6) { - return 0.8483703756831885e18; // 90,000,000 0.02 - } else { - return 0.8840300662301638e18; // 90,000,000 0.015 - } + if (recapPercentPaid > 0.23e6) { + return 0.5706967827806866e18; // 90,000,000, 0.25 } else { - if (recapPercentPaid > 0.005e6) { - return 0.921148979567821e18; // 90,000,000 0.01 - } else { - return 0.9597854268015467e18; // 90,000,000 0.005 - } + return 0.5910297971598633e18; // 90,000,000, 0.23 } } - } - } else { - // > 5,000,000 - if (recapPercentPaid > 0.1e6) { - if (recapPercentPaid > 0.21e6) { - if (recapPercentPaid > 0.38e6) { - if (recapPercentPaid > 0.45e6) { - return 0.000340444522821781e18; // 10,000,000, 0.9 - } else { - return 0.04023093970853808e18; // 10,000,000, 0.45 - } - } else if (recapPercentPaid > 0.29e6) { - if (recapPercentPaid > 0.33e6) { - return 0.06954881077191022e18; // 10,000,000 0.38 - } else { - return 0.10145116013499655e18; // 10,000,000 0.33 - } - } else if (recapPercentPaid > 0.25e6) { - if (recapPercentPaid > 0.27e6) { - return 0.13625887314323348e18; // 10,000,000 0.29 - } else { - return 0.15757224609763754e18; // 10,000,000 0.27 - } + } else { + if (recapPercentPaid > 0.17e6) { + if (recapPercentPaid > 0.19e6) { + return 0.6128602937515535e18; // 90,000,000, 0.21 } else { - if (recapPercentPaid > 0.23e6) { - return 0.18197183407669726e18; // 10,000,000 0.25 - } else { - return 0.20987581330872107e18; // 10,000,000 0.23 - } + return 0.6363596297698088e18; // 90,000,000, 0.19 } - } else { - if (recapPercentPaid > 0.17e6) { - if (recapPercentPaid > 0.19e6) { - return 0.24175584233885106e18; // 10,000,000, 0.21 - } else { - return 0.27814356260741413e18; // 10,000,000, 0.19 - } - } else if (recapPercentPaid > 0.14e6) { - if (recapPercentPaid > 0.15e6) { - return 0.3196378540296301e18; // 10,000,000 0.17 - } else { - return 0.36691292973511136e18; // 10,000,000 0.15 - } - } else if (recapPercentPaid > 0.1e6) { - if (recapPercentPaid > 0.13e6) { - return 0.3929517529835418e18; // 10,000,000 0.14 - } else { - return 0.4207273631610372e18; // 10,000,000 0.13 - } + } else if (recapPercentPaid > 0.14e6) { + if (recapPercentPaid > 0.15e6) { + return 0.6617262928282552e18; // 90,000,000, 0.17 } else { - if (recapPercentPaid > 0.11e6) { - return 0.450349413795883e18; // 10,000,000 0.12 - } else { - return 0.4819341506654745e18; // 10,000,000 0.11 - } + return 0.6891914824733962e18; // 90,000,000, 0.15 } - } - } else { - if (recapPercentPaid > 0.04e6) { - if (recapPercentPaid > 0.08e6) { - if (recapPercentPaid > 0.09e6) { - return 0.5156047910307769e18; // 10,000,000, 0.1 - } else { - return 0.551491923831086e18; // 10,000,000, 0.09 - } - } else if (recapPercentPaid > 0.06e6) { - if (recapPercentPaid > 0.07e6) { - return 0.5897339319558434e18; // 10,000,000 0.08 - } else { - return 0.6304774377677631e18; // 10,000,000 0.07 - } - } else if (recapPercentPaid > 0.05e6) { - if (recapPercentPaid > 0.055e6) { - return 0.6738777731119263e18; // 10,000,000 0.06 - } else { - return 0.6966252960203008e18; // 10,000,000 0.055 - } + } else if (recapPercentPaid > 0.12e6) { + if (recapPercentPaid > 0.13e6) { + return 0.7037939098015373e18; // 90,000,000, 0.14 } else { - if (recapPercentPaid > 0.045e6) { - return 0.7200994751088836e18; // 10,000,000 0.05 - } else { - return 0.7443224016328813e18; // 10,000,000 0.045 - } + return 0.719026126689054e18; // 90,000,000, 0.13 } } else { - if (recapPercentPaid > 0.03e6) { - if (recapPercentPaid > 0.035e6) { - return 0.7693168090963867e18; // 10,000,000, 0.04 - } else { - return 0.7951060911805916e18; // 10,000,000, 0.035 - } - } else if (recapPercentPaid > 0.02e6) { - if (recapPercentPaid > 0.025e6) { - return 0.8217143201541763e18; // 10,000,000 0.03 - } else { - return 0.8491662657783823e18; // 10,000,000 0.025 - } - } else if (recapPercentPaid > 0.01e6) { - if (recapPercentPaid > 0.015e6) { - return 0.8774874147196358e18; // 10,000,000 0.02 - } else { - return 0.9067039904828691e18; // 10,000,000 0.015 - } + if (recapPercentPaid > 0.11e6) { + return 0.7349296649399273e18; // 90,000,000, 0.12 } else { - if (recapPercentPaid > 0.005e6) { - return 0.9368429738790524e18; // 10,000,000 0.01 - } else { - return 0.9679321240407666e18; // 10,000,000 0.005 - } + return 0.7515497824365694e18; // 90,000,000, 0.11 } } } + } else { + if (recapPercentPaid > 0.08e6) { + if (recapPercentPaid > 0.09e6) { + return 0.7689358898389307e18; // 90,000,000, 0.1 + } else { + return 0.7871420372030031e18; // 90,000,000, 0.09 + } + } else if (recapPercentPaid > 0.06e6) { + if (recapPercentPaid > 0.07e6) { + return 0.8062274705566613e18; // 90,000,000, 0.08 + } else { + return 0.8262572704372576e18; // 90,000,000, 0.07 + } + } else if (recapPercentPaid > 0.05e6) { + if (recapPercentPaid > 0.055e6) { + return 0.8473030868055568e18; // 90,000,000, 0.06 + } else { + return 0.8582313943058512e18; // 90,000,000, 0.055 + } + } else if (recapPercentPaid > 0.04e6) { + if (recapPercentPaid > 0.045e6) { + return 0.8694439877186144e18; // 90,000,000, 0.05 + } else { + return 0.8809520709014887e18; // 90,000,000, 0.045 + } + } + if (recapPercentPaid > 0.03e6) { + if (recapPercentPaid > 0.035e6) { + return 0.892767442816813e18; // 90,000,000, 0.04 + } else { + return 0.9049025374937268e18; // 90,000,000, 0.035 + } + } else if (recapPercentPaid > 0.02e6) { + if (recapPercentPaid > 0.025e6) { + return 0.9173704672485867e18; // 90,000,000, 0.03 + } else { + return 0.9301850694774185e18; // 90,000,000, 0.025 + } + } else if (recapPercentPaid > 0.01e6) { + if (recapPercentPaid > 0.015e6) { + return 0.9433609573691148e18; // 90,000,000, 0.02 + } else { + return 0.9569135749274008e18; // 90,000,000, 0.015 + } + } else { + if (recapPercentPaid > 0.005e6) { + return 0.9708592567341514e18; // 90,000,000, 0.01 + } else { + return 0.9852152929368606e18; // 90,000,000, 0.005 + } + } } - } else { - if (unripeSupply > 1_000_000) { - if (recapPercentPaid > 0.1e6) { - if (recapPercentPaid > 0.21e6) { - if (recapPercentPaid > 0.38e6) { - if (recapPercentPaid > 0.45e6) { - return 0.000946395082480844e18; // 3,000,000, 0.9 - } else { - return 0.06786242725985348e18; // 3,000,000, 0.45 - } - } else if (recapPercentPaid > 0.29e6) { - if (recapPercentPaid > 0.33e6) { - return 0.10822315472628707e18; // 3,000,000 0.38 - } else { - return 0.14899524306327216e18; // 3,000,000 0.33 - } - } else if (recapPercentPaid > 0.25e6) { - if (recapPercentPaid > 0.27e6) { - return 0.1910488239684135e18; // 3,000,000 0.29 - } else { - return 0.215863137234529e18; // 3,000,000 0.27 - } + } else if (unripeSupply > 10_000_000) { + if (recapPercentPaid > 0.1e6) { + if (recapPercentPaid > 0.21e6) { + if (recapPercentPaid > 0.38e6) { + if (recapPercentPaid > 0.45e6) { + return 0.2601562129458128e18; // 10,000,000, 0.9 + } else { + return 0.41636482361397587e18; // 10,000,000, 0.45 + } + } else if (recapPercentPaid > 0.29e6) { + if (recapPercentPaid > 0.33e6) { + return 0.4587658967980477e18; // 10,000,000, 0.38 + } else { + return 0.49461012289361284e18; // 10,000,000, 0.33 + } + } else if (recapPercentPaid > 0.25e6) { + if (recapPercentPaid > 0.27e6) { + return 0.5274727741119862e18; // 10,000,000, 0.29 } else { - if (recapPercentPaid > 0.23e6) { - return 0.243564628757033e18; // 3,000,000 0.25 - } else { - return 0.2744582675491247e18; // 3,000,000 0.23 - } + return 0.5455524222086705e18; // 10,000,000, 0.27 } } else { - if (recapPercentPaid > 0.17e6) { - if (recapPercentPaid > 0.19e6) { - return 0.3088786047254358e18; // 3,000,000, 0.21 - } else { - return 0.3471924328319608e18; // 3,000,000, 0.19 - } - } else if (recapPercentPaid > 0.14e6) { - if (recapPercentPaid > 0.15e6) { - return 0.38980166833777796e18; // 3,000,000 0.17 - } else { - return 0.4371464748698771e18; // 3,000,000 0.15 - } - } else if (recapPercentPaid > 0.12e6) { - if (recapPercentPaid > 0.13e6) { - return 0.46274355346663876e18; // 3,000,000 0.14 - } else { - return 0.4897086460787351e18; // 3,000,000 0.13 - } + if (recapPercentPaid > 0.23e6) { + return 0.5648800673771895e18; // 10,000,000, 0.25 } else { - if (recapPercentPaid > 0.11e6) { - return 0.518109082463349e18; // 3,000,000 0.12 - } else { - return 0.5480152684204499e18; // 3,000,000 0.11 - } + return 0.5855868704094357e18; // 10,000,000, 0.23 } } } else { - if (recapPercentPaid > 0.04e6) { - if (recapPercentPaid > 0.08e6) { - if (recapPercentPaid > 0.09e6) { - return 0.5795008171102514e18; // 3,000,000, 0.1 - } else { - return 0.6126426856374751e18; // 3,000,000, 0.09 - } - } else if (recapPercentPaid > 0.06e6) { - if (recapPercentPaid > 0.07e6) { - return 0.6475213171017626e18; // 3,000,000 0.08 - } else { - return 0.6842207883207123e18; // 3,000,000 0.07 - } - } else if (recapPercentPaid > 0.05e6) { - if (recapPercentPaid > 0.055e6) { - return 0.7228289634394097e18; // 3,000,000 0.06 - } else { - return 0.742877347280416e18; // 3,000,000 0.055 - } + if (recapPercentPaid > 0.17e6) { + if (recapPercentPaid > 0.19e6) { + return 0.6078227259058706e18; // 10,000,000, 0.21 + } else { + return 0.631759681239449e18; // 10,000,000, 0.19 + } + } else if (recapPercentPaid > 0.14e6) { + if (recapPercentPaid > 0.15e6) { + return 0.6575961226208655e18; // 10,000,000, 0.17 } else { - if (recapPercentPaid > 0.045e6) { - return 0.7634376536479606e18; // 3,000,000 0.05 - } else { - return 0.784522002909275e18; // 3,000,000 0.045 - } + return 0.68556193437231e18; // 10,000,000, 0.15 + } + } else if (recapPercentPaid > 0.12e6) { + if (recapPercentPaid > 0.13e6) { + return 0.7004253506676488e18; // 10,000,000, 0.14 + } else { + return 0.7159249025906607e18; // 10,000,000, 0.13 } } else { - if (recapPercentPaid > 0.03e6) { - if (recapPercentPaid > 0.035e6) { - return 0.8061427832364296e18; // 3,000,000, 0.04 - } else { - return 0.8283126561589187e18; // 3,000,000, 0.035 - } - } else if (recapPercentPaid > 0.02e6) { - if (recapPercentPaid > 0.025e6) { - return 0.8510445622247672e18; // 3,000,000 0.03 - } else { - return 0.8743517267721741e18; // 3,000,000 0.025 - } - } else if (recapPercentPaid > 0.01e6) { - if (recapPercentPaid > 0.015e6) { - return 0.8982476658137254e18; // 3,000,000 0.02 - } else { - return 0.9227461920352636e18; // 3,000,000 0.015 - } + if (recapPercentPaid > 0.11e6) { + return 0.7321012978270447e18; // 10,000,000, 0.12 } else { - if (recapPercentPaid > 0.005e6) { - return 0.9478614209115208e18; // 3,000,000 0.01 - } else { - return 0.9736077769406731e18; // 3,000,000 0.005 - } + return 0.7489987232590216e18; // 10,000,000, 0.11 } } } } else { - if (recapPercentPaid > 0.1e6) { - if (recapPercentPaid > 0.21e6) { - if (recapPercentPaid > 0.38e6) { - if (recapPercentPaid > 0.45e6) { - return 0.003360632002379016e18; // 1,000,000, 0.9 - } else { - return 0.12071031956650236e18; // 1,000,000, 0.45 - } - } else if (recapPercentPaid > 0.29e6) { - if (recapPercentPaid > 0.33e6) { - return 0.1752990554517151e18; // 1,000,000 0.38 - } else { - return 0.22598948369141458e18; // 1,000,000 0.33 - } - } else if (recapPercentPaid > 0.25e6) { - if (recapPercentPaid > 0.27e6) { - return 0.27509697387157794e18; // 1,000,000 0.29 - } else { - return 0.3029091410266461e18; // 1,000,000 0.27 - } + if (recapPercentPaid > 0.08e6) { + if (recapPercentPaid > 0.09e6) { + return 0.766665218442354e18; // 10,000,000, 0.1 + } else { + return 0.7851530975272665e18; // 10,000,000, 0.09 + } + } else if (recapPercentPaid > 0.06e6) { + if (recapPercentPaid > 0.07e6) { + return 0.8045194270172396e18; // 10,000,000, 0.08 + } else { + return 0.8248265680621683e18; // 10,000,000, 0.07 + } + } else if (recapPercentPaid > 0.05e6) { + if (recapPercentPaid > 0.055e6) { + return 0.8461427935458878e18; // 10,000,000, 0.06 + } else { + return 0.8572024359670631e18; // 10,000,000, 0.055 + } + } else if (recapPercentPaid > 0.04e6) { + if (recapPercentPaid > 0.045e6) { + return 0.8685429921113414e18; // 10,000,000, 0.05 + } else { + return 0.8801749888510111e18; // 10,000,000, 0.045 + } + } + if (recapPercentPaid > 0.03e6) { + if (recapPercentPaid > 0.035e6) { + return 0.8921094735432339e18; // 10,000,000, 0.04 + } else { + return 0.9043580459814082e18; // 10,000,000, 0.035 + } + } else if (recapPercentPaid > 0.02e6) { + if (recapPercentPaid > 0.025e6) { + return 0.9169328926903124e18; // 10,000,000, 0.03 + } else { + return 0.9298468237651341e18; // 10,000,000, 0.025 + } + } else if (recapPercentPaid > 0.01e6) { + if (recapPercentPaid > 0.015e6) { + return 0.9431133124739901e18; // 10,000,000, 0.02 + } else if (recapPercentPaid > 0.01e6) { + return 0.956746537865208e18; // 10,000,000, 0.015 + } else if (recapPercentPaid > 0.005e6) { + return 0.970761430644659e18; // 10,000,000, 0.01 + } else { + return 0.9851737226151924e18; // 10,000,000, 0.005 + } + } + } + } else if (unripeSupply > 1_000_000) { + if (recapPercentPaid > 0.1e6) { + if (recapPercentPaid > 0.21e6) { + if (recapPercentPaid > 0.38e6) { + if (recapPercentPaid > 0.45e6) { + return 0.22204456672314377e18; // 1,000,000, 0.9 + } else { + return 0.4085047499499631e18; // 1,000,000, 0.45 + } + } else if (recapPercentPaid > 0.29e6) { + if (recapPercentPaid > 0.33e6) { + return 0.46027376814120946e18; // 1,000,000, 0.38 + } else { + return 0.5034753937446597e18; // 1,000,000, 0.33 + } + } else if (recapPercentPaid > 0.25e6) { + if (recapPercentPaid > 0.27e6) { + return 0.5424140302842413e18; // 1,000,000, 0.29 } else { - if (recapPercentPaid > 0.23e6) { - return 0.33311222196618273e18; // 1,000,000 0.25 - } else { - return 0.36588364748950297e18; // 1,000,000 0.23 - } + return 0.5635119158156667e18; // 1,000,000, 0.27 } } else { - if (recapPercentPaid > 0.17e6) { - if (recapPercentPaid > 0.19e6) { - return 0.40141235983370593e18; // 1,000,000, 0.21 - } else { - return 0.43989947169522015e18; // 1,000,000, 0.19 - } - } else if (recapPercentPaid > 0.14e6) { - if (recapPercentPaid > 0.15e6) { - return 0.4815589587559236e18; // 1,000,000 0.17 - } else { - return 0.5266183872325827e18; // 1,000,000 0.15 - } - } else if (recapPercentPaid > 0.12e6) { - if (recapPercentPaid > 0.13e6) { - return 0.5504980973828455e18; // 1,000,000 0.14 - } else { - return 0.5753196780298556e18; // 1,000,000 0.13 - } + if (recapPercentPaid > 0.23e6) { + return 0.5857864256253713e18; // 1,000,000, 0.25 } else { - if (recapPercentPaid > 0.11e6) { - return 0.6011157438454372e18; // 1,000,000 0.12 - } else { - return 0.6279199091408495e18; // 1,000,000 0.11 - } + return 0.6093112868361505e18; // 1,000,000, 0.23 } } } else { - if (recapPercentPaid > 0.04e6) { - if (recapPercentPaid > 0.08e6) { - if (recapPercentPaid > 0.09e6) { - return 0.6557668151543954e18; // 1,000,000, 0.1 - } else { - return 0.6846921580052533e18; // 1,000,000, 0.09 - } - } else if (recapPercentPaid > 0.06e6) { - if (recapPercentPaid > 0.07e6) { - return 0.7147327173281093e18; // 1,000,000 0.08 - } else { - return 0.745926385603471e18; // 1,000,000 0.07 - } - } else if (recapPercentPaid > 0.05e6) { - if (recapPercentPaid > 0.055e6) { - return 0.7783121981988174e18; // 1,000,000 0.06 - } else { - return 0.7949646772335068e18; // 1,000,000 0.055 - } + if (recapPercentPaid > 0.17e6) { + if (recapPercentPaid > 0.19e6) { + return 0.6341650041820726e18; // 1,000,000, 0.21 + } else { + return 0.6604311671564058e18; // 1,000,000, 0.19 + } + } else if (recapPercentPaid > 0.14e6) { + if (recapPercentPaid > 0.15e6) { + return 0.6881987762208012e18; // 1,000,000, 0.17 } else { - if (recapPercentPaid > 0.045e6) { - return 0.8119303641360465e18; // 1,000,000 0.05 - } else { - return 0.8292144735871585e18; // 1,000,000 0.045 - } + return 0.7175625891924777e18; // 1,000,000, 0.15 + } + } else if (recapPercentPaid > 0.12e6) { + if (recapPercentPaid > 0.13e6) { + return 0.7328743482797107e18; // 1,000,000, 0.14 + } else { + return 0.7486234889866461e18; // 1,000,000, 0.13 } } else { - if (recapPercentPaid > 0.03e6) { - if (recapPercentPaid > 0.035e6) { - return 0.8468222976009872e18; // 1,000,000, 0.04 - } else { - return 0.8647592065514869e18; // 1,000,000, 0.035 - } - } else if (recapPercentPaid > 0.02e6) { - if (recapPercentPaid > 0.025e6) { - return 0.8830306502110374e18; // 1,000,000 0.03 - } else { - return 0.9016421588014247e18; // 1,000,000 0.025 - } - } else if (recapPercentPaid > 0.01e6) { - if (recapPercentPaid > 0.015e6) { - return 0.9205993440573136e18; // 1,000,000 0.02 - } else { - return 0.9399079003023474e18; // 1,000,000 0.015 - } + if (recapPercentPaid > 0.11e6) { + return 0.7648236427602255e18; // 1,000,000, 0.12 } else { - if (recapPercentPaid > 0.005e6) { - return 0.959573605538012e18; // 1,000,000 0.01 - } else { - return 0.9796023225453983e18; // 1,000,000 0.005 - } + return 0.7814888739548376e18; // 1,000,000, 0.11 } } } + } else { + if (recapPercentPaid > 0.08e6) { + if (recapPercentPaid > 0.09e6) { + return 0.798633693358723e18; // 1,000,000, 0.1 + } else { + return 0.8162730721263407e18; // 1,000,000, 0.09 + } + } else if (recapPercentPaid > 0.06e6) { + if (recapPercentPaid > 0.07e6) { + return 0.8344224561281671e18; // 1,000,000, 0.08 + } else { + return 0.8530977807297004e18; // 1,000,000, 0.07 + } + } else if (recapPercentPaid > 0.05e6) { + if (recapPercentPaid > 0.055e6) { + return 0.8723154860117406e18; // 1,000,000, 0.06 + } else { + return 0.8821330107890434e18; // 1,000,000, 0.055 + } + } else if (recapPercentPaid > 0.04e6) { + if (recapPercentPaid > 0.045e6) { + return 0.8920925324443344e18; // 1,000,000, 0.05 + } else { + return 0.9021962549951718e18; // 1,000,000, 0.045 + } + } + if (recapPercentPaid > 0.03e6) { + if (recapPercentPaid > 0.035e6) { + return 0.9124464170270961e18; // 1,000,000, 0.04 + } else { + return 0.9228452922244391e18; // 1,000,000, 0.035 + } + } else if (recapPercentPaid > 0.02e6) { + if (recapPercentPaid > 0.025e6) { + return 0.9333951899089395e18; // 1,000,000, 0.03 + } else { + return 0.9440984555862713e18; // 1,000,000, 0.025 + } + } else if (recapPercentPaid > 0.01e6) { + if (recapPercentPaid > 0.015e6) { + return 0.9549574715005937e18; // 1,000,000, 0.02 + } else if (recapPercentPaid > 0.01e6) { + return 0.9659746571972349e18; // 1,000,000, 0.015 + } else if (recapPercentPaid > 0.005e6) { + return 0.9771524700936202e18; // 1,000,000, 0.01 + } else { + return 0.988493406058558e18; // 1,000,000, 0.005 + } + } } } } diff --git a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js index 07a293800c..724b3b3d57 100644 --- a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js +++ b/protocol/contracts/libraries/code_generation/generate_locked_underlying.js @@ -41,19 +41,19 @@ function generateNestedBlocks(items, unripeSupply) { let marker = 16; let market16close = false; if (counter % marker == 0 && counter + marker < items.length) { - let recentPercentPaid = items[counter + marker].recapPercentPaid; + let recapPercentPaid = items[counter + marker].recapPercentPaid; code += - tab1 + "if (recentPercentPaid > " + recentPercentPaid + "e18) {\n"; + tab1 + "if (recapPercentPaid > " + recapPercentPaid + "e6) {\n"; market16close = true; } marker = 8; if (counter % marker == 0) { if (counter < marker) { - let recentPercentPaid = items[counter + marker].recapPercentPaid; + let recapPercentPaid = items[counter + marker].recapPercentPaid; code += tab2 + - "if (recentPercentPaid > " + - recentPercentPaid + + "if (recapPercentPaid > " + + recapPercentPaid + "e6) {\n"; } @@ -65,11 +65,11 @@ function generateNestedBlocks(items, unripeSupply) { // if i mod 2 == 0, open an if statement (3rd layer of ifs) if (i % 2 == 0) { if (counter + i + 2 < items.length && counter + i + 2 > 0) { - let recentPercentPaid = items[counter + i + 2].recapPercentPaid; + let recapPercentPaid = items[counter + i + 2].recapPercentPaid; if (i % 8 == 0) { - code += tab3 + "if (recentPercentPaid > " + recentPercentPaid + "e6) {\n"; + code += tab3 + "if (recapPercentPaid > " + recapPercentPaid + "e6) {\n"; } else if (i % 8 < 6) { - code += tab3 + "} else if (recentPercentPaid > " + recentPercentPaid + "e6) {\n"; + code += tab3 + "} else if (recapPercentPaid > " + recapPercentPaid + "e6) {\n"; } else { code += tab3 + "} else {\n"; } @@ -81,8 +81,8 @@ function generateNestedBlocks(items, unripeSupply) { // if even if (i % 2 == 0) { - let recentPercentPaid = items[counter + i + 1].recapPercentPaid; - code += tab4 + "if (recentPercentPaid > " + recentPercentPaid + "e6) {\n"; + let recapPercentPaid = items[counter + i + 1].recapPercentPaid; + code += tab4 + "if (recapPercentPaid > " + recapPercentPaid + "e6) {\n"; } else { code += tab4 + "} else {\n"; } @@ -94,10 +94,14 @@ function generateNestedBlocks(items, unripeSupply) { } } - code += tab3 + "}\n"; // close 8-level if + code += tab3 + "} // closed 8 level if " + counter + "\n"; // close 8-level if + if (counter == 8) { + code += tab2 + "}\n"; + code += tab1 + "} else { // close upper level\n"; + } } if (market16close) { - code += tab2 + "} else {\n"; // close 16-level if + code += tab2 + "} else { // close 16 level if \n"; // close 16-level if } counter++; @@ -115,11 +119,22 @@ let code = ""; const unripeSupplyValues = [90000000, 10000000, 1000000]; +let groupCount = 0; for (const unripeSupply of unripeSupplyValues) { const items = groupedData[unripeSupply]; if (!items) continue; - code += `if (unripeSupply > ${unripeSupply}) {\n`; + const unripeFormatted = unripeSupply.toLocaleString('en-US', { + useGrouping: true, + groupingSeparator: '_' + }); + + if (groupCount == 0) { + code += `if (unripeSupply > ${unripeFormatted}) {\n`; + } else { + code += `if (unripeSupply < ${unripeFormatted}) {\n`; + } + groupCount++; items.sort((a, b) => b.recapPercentPaid - a.recapPercentPaid); code += generateNestedBlocks(items, unripeSupply); code += `} else `; diff --git a/protocol/contracts/libraries/code_generation/generated_code.sol b/protocol/contracts/libraries/code_generation/generated_code.sol deleted file mode 100644 index 199040a3db..0000000000 --- a/protocol/contracts/libraries/code_generation/generated_code.sol +++ /dev/null @@ -1,318 +0,0 @@ -if (unripeSupply > 90000000) { - if (recentPercentPaid > 0.1e18) { - if (recentPercentPaid > 0.21e6) { - if (recentPercentPaid > 0.38e6) { - if (recentPercentPaid > 0.45e6) { - return 0.2691477202198985e18; // 90,000,000, 0.9 - } else { - return 0.4245158057296602e18; // 90,000,000, 0.45 - } - } else if (recentPercentPaid > 0.29000000000000004e6) { - if (recentPercentPaid > 0.33e6) { - return 0.46634353868138156e18; // 90,000,000, 0.38 - } else { - return 0.5016338055689489e18; // 90,000,000, 0.33 - } - } else if (recentPercentPaid > 0.25e6) { - if (recentPercentPaid > 0.27e6) { - return 0.5339474169852891e18; // 90,000,000, 0.29000000000000004 - } else { - return 0.5517125463928281e18; // 90,000,000, 0.27 - } - } else { - if (recentPercentPaid > 0.22999999999999998e6) { - return 0.5706967827806866e18; // 90,000,000, 0.25 - } else { - return 0.5910297971598633e18; // 90,000,000, 0.22999999999999998 - } - } - } else { - if (recentPercentPaid > 0.16999999999999998e6) { - if (recentPercentPaid > 0.19e6) { - return 0.6128602937515535e18; // 90,000,000, 0.21 - } else { - return 0.6363596297698088e18; // 90,000,000, 0.19 - } - } else if (recentPercentPaid > 0.14e6) { - if (recentPercentPaid > 0.15e6) { - return 0.6617262928282552e18; // 90,000,000, 0.16999999999999998 - } else { - return 0.6891914824733962e18; // 90,000,000, 0.15 - } - } else if (recentPercentPaid > 0.12e6) { - if (recentPercentPaid > 0.13e6) { - return 0.7037939098015373e18; // 90,000,000, 0.14 - } else { - return 0.719026126689054e18; // 90,000,000, 0.13 - } - } else { - if (recentPercentPaid > 0.11e6) { - return 0.7349296649399273e18; // 90,000,000, 0.12 - } else { - return 0.7515497824365694e18; // 90,000,000, 0.11 - } - } - if (recentPercentPaid > 0.08e6) { - if (recentPercentPaid > 0.09e6) { - return 0.7689358898389307e18; // 90,000,000, 0.1 - } else { - return 0.7871420372030031e18; // 90,000,000, 0.09 - } - } else if (recentPercentPaid > 0.06e6) { - if (recentPercentPaid > 0.06999999999999999e6) { - return 0.8062274705566613e18; // 90,000,000, 0.08 - } else { - return 0.8262572704372576e18; // 90,000,000, 0.06999999999999999 - } - } else if (recentPercentPaid > 0.05e6) { - if (recentPercentPaid > 0.055e6) { - return 0.8473030868055568e18; // 90,000,000, 0.06 - } else { - return 0.8582313943058512e18; // 90,000,000, 0.055 - } - } else { - if (recentPercentPaid > 0.045e6) { - return 0.8694439877186144e18; // 90,000,000, 0.05 - } else { - return 0.8809520709014887e18; // 90,000,000, 0.045 - } - } - if (recentPercentPaid > 0.03e6) { - if (recentPercentPaid > 0.035e6) { - return 0.892767442816813e18; // 90,000,000, 0.04 - } else { - return 0.9049025374937268e18; // 90,000,000, 0.035 - } - } else if (recentPercentPaid > 0.02e6) { - if (recentPercentPaid > 0.025e6) { - return 0.9173704672485867e18; // 90,000,000, 0.03 - } else { - return 0.9301850694774185e18; // 90,000,000, 0.025 - } - } else if (recentPercentPaid > 0.01e6) { - if (recentPercentPaid > 0.015e6) { - return 0.9433609573691148e18; // 90,000,000, 0.02 - } else { - return 0.9569135749274008e18; // 90,000,000, 0.015 - } - if (recentPercentPaid > 0.005e6) { - return 0.9708592567341514e18; // 90,000,000, 0.01 - } else { - return 0.9852152929368606e18; // 90,000,000, 0.005 - } - } - } - } -} else if (unripeSupply > 10000000) { - if (recentPercentPaid > 0.1e18) { - if (recentPercentPaid > 0.21e6) { - if (recentPercentPaid > 0.38e6) { - if (recentPercentPaid > 0.45e6) { - return 0.2601562129458128e18; // 10,000,000, 0.9 - } else { - return 0.41636482361397587e18; // 10,000,000, 0.45 - } - } else if (recentPercentPaid > 0.29000000000000004e6) { - if (recentPercentPaid > 0.33e6) { - return 0.4587658967980477e18; // 10,000,000, 0.38 - } else { - return 0.49461012289361284e18; // 10,000,000, 0.33 - } - } else if (recentPercentPaid > 0.25e6) { - if (recentPercentPaid > 0.27e6) { - return 0.5274727741119862e18; // 10,000,000, 0.29000000000000004 - } else { - return 0.5455524222086705e18; // 10,000,000, 0.27 - } - } else { - if (recentPercentPaid > 0.22999999999999998e6) { - return 0.5648800673771895e18; // 10,000,000, 0.25 - } else { - return 0.5855868704094357e18; // 10,000,000, 0.22999999999999998 - } - } - } else { - if (recentPercentPaid > 0.16999999999999998e6) { - if (recentPercentPaid > 0.19e6) { - return 0.6078227259058706e18; // 10,000,000, 0.21 - } else { - return 0.631759681239449e18; // 10,000,000, 0.19 - } - } else if (recentPercentPaid > 0.14e6) { - if (recentPercentPaid > 0.15e6) { - return 0.6575961226208655e18; // 10,000,000, 0.16999999999999998 - } else { - return 0.68556193437231e18; // 10,000,000, 0.15 - } - } else if (recentPercentPaid > 0.12e6) { - if (recentPercentPaid > 0.13e6) { - return 0.7004253506676488e18; // 10,000,000, 0.14 - } else { - return 0.7159249025906607e18; // 10,000,000, 0.13 - } - } else { - if (recentPercentPaid > 0.11e6) { - return 0.7321012978270447e18; // 10,000,000, 0.12 - } else { - return 0.7489987232590216e18; // 10,000,000, 0.11 - } - } - if (recentPercentPaid > 0.08e6) { - if (recentPercentPaid > 0.09e6) { - return 0.766665218442354e18; // 10,000,000, 0.1 - } else { - return 0.7851530975272665e18; // 10,000,000, 0.09 - } - } else if (recentPercentPaid > 0.06e6) { - if (recentPercentPaid > 0.06999999999999999e6) { - return 0.8045194270172396e18; // 10,000,000, 0.08 - } else { - return 0.8248265680621683e18; // 10,000,000, 0.06999999999999999 - } - } else if (recentPercentPaid > 0.05e6) { - if (recentPercentPaid > 0.055e6) { - return 0.8461427935458878e18; // 10,000,000, 0.06 - } else { - return 0.8572024359670631e18; // 10,000,000, 0.055 - } - } else { - if (recentPercentPaid > 0.045e6) { - return 0.8685429921113414e18; // 10,000,000, 0.05 - } else { - return 0.8801749888510111e18; // 10,000,000, 0.045 - } - } - if (recentPercentPaid > 0.03e6) { - if (recentPercentPaid > 0.035e6) { - return 0.8921094735432339e18; // 10,000,000, 0.04 - } else { - return 0.9043580459814082e18; // 10,000,000, 0.035 - } - } else if (recentPercentPaid > 0.02e6) { - if (recentPercentPaid > 0.025e6) { - return 0.9169328926903124e18; // 10,000,000, 0.03 - } else { - return 0.9298468237651341e18; // 10,000,000, 0.025 - } - } else if (recentPercentPaid > 0.01e6) { - if (recentPercentPaid > 0.015e6) { - return 0.9431133124739901e18; // 10,000,000, 0.02 - } else { - return 0.956746537865208e18; // 10,000,000, 0.015 - } - if (recentPercentPaid > 0.005e6) { - return 0.970761430644659e18; // 10,000,000, 0.01 - } else { - return 0.9851737226151924e18; // 10,000,000, 0.005 - } - } - } - } -} else if (unripeSupply > 1000000) { - if (recentPercentPaid > 0.1e18) { - if (recentPercentPaid > 0.21e6) { - if (recentPercentPaid > 0.38e6) { - if (recentPercentPaid > 0.45e6) { - return 0.22204456672314377e18; // 1,000,000, 0.9 - } else { - return 0.4085047499499631e18; // 1,000,000, 0.45 - } - } else if (recentPercentPaid > 0.29000000000000004e6) { - if (recentPercentPaid > 0.33e6) { - return 0.46027376814120946e18; // 1,000,000, 0.38 - } else { - return 0.5034753937446597e18; // 1,000,000, 0.33 - } - } else if (recentPercentPaid > 0.25e6) { - if (recentPercentPaid > 0.27e6) { - return 0.5424140302842413e18; // 1,000,000, 0.29000000000000004 - } else { - return 0.5635119158156667e18; // 1,000,000, 0.27 - } - } else { - if (recentPercentPaid > 0.22999999999999998e6) { - return 0.5857864256253713e18; // 1,000,000, 0.25 - } else { - return 0.6093112868361505e18; // 1,000,000, 0.22999999999999998 - } - } - } else { - if (recentPercentPaid > 0.16999999999999998e6) { - if (recentPercentPaid > 0.19e6) { - return 0.6341650041820726e18; // 1,000,000, 0.21 - } else { - return 0.6604311671564058e18; // 1,000,000, 0.19 - } - } else if (recentPercentPaid > 0.14e6) { - if (recentPercentPaid > 0.15e6) { - return 0.6881987762208012e18; // 1,000,000, 0.16999999999999998 - } else { - return 0.7175625891924777e18; // 1,000,000, 0.15 - } - } else if (recentPercentPaid > 0.12e6) { - if (recentPercentPaid > 0.13e6) { - return 0.7328743482797107e18; // 1,000,000, 0.14 - } else { - return 0.7486234889866461e18; // 1,000,000, 0.13 - } - } else { - if (recentPercentPaid > 0.11e6) { - return 0.7648236427602255e18; // 1,000,000, 0.12 - } else { - return 0.7814888739548376e18; // 1,000,000, 0.11 - } - } - if (recentPercentPaid > 0.08e6) { - if (recentPercentPaid > 0.09e6) { - return 0.798633693358723e18; // 1,000,000, 0.1 - } else { - return 0.8162730721263407e18; // 1,000,000, 0.09 - } - } else if (recentPercentPaid > 0.06e6) { - if (recentPercentPaid > 0.06999999999999999e6) { - return 0.8344224561281671e18; // 1,000,000, 0.08 - } else { - return 0.8530977807297004e18; // 1,000,000, 0.06999999999999999 - } - } else if (recentPercentPaid > 0.05e6) { - if (recentPercentPaid > 0.055e6) { - return 0.8723154860117406e18; // 1,000,000, 0.06 - } else { - return 0.8821330107890434e18; // 1,000,000, 0.055 - } - } else { - if (recentPercentPaid > 0.045e6) { - return 0.8920925324443344e18; // 1,000,000, 0.05 - } else { - return 0.9021962549951718e18; // 1,000,000, 0.045 - } - } - if (recentPercentPaid > 0.03e6) { - if (recentPercentPaid > 0.035e6) { - return 0.9124464170270961e18; // 1,000,000, 0.04 - } else { - return 0.9228452922244391e18; // 1,000,000, 0.035 - } - } else if (recentPercentPaid > 0.02e6) { - if (recentPercentPaid > 0.025e6) { - return 0.9333951899089395e18; // 1,000,000, 0.03 - } else { - return 0.9440984555862713e18; // 1,000,000, 0.025 - } - } else if (recentPercentPaid > 0.01e6) { - if (recentPercentPaid > 0.015e6) { - return 0.9549574715005937e18; // 1,000,000, 0.02 - } else { - return 0.9659746571972349e18; // 1,000,000, 0.015 - } - if (recentPercentPaid > 0.005e6) { - return 0.9771524700936202e18; // 1,000,000, 0.01 - } else { - return 0.988493406058558e18; // 1,000,000, 0.005 - } - } - } - } -} else { - return 0; // If < 1,000,000 Assume all supply is unlocked. -} \ No newline at end of file diff --git a/protocol/contracts/libraries/code_generation/updated_numbers.csv b/protocol/contracts/libraries/code_generation/updated_numbers.csv index 56707dc844..cfaef91a33 100644 --- a/protocol/contracts/libraries/code_generation/updated_numbers.csv +++ b/protocol/contracts/libraries/code_generation/updated_numbers.csv @@ -10,7 +10,7 @@ 0.05, 1000000, 0.8920925324443344 0.055, 1000000, 0.8821330107890434 0.06, 1000000, 0.8723154860117406 -0.06999999999999999, 1000000, 0.8530977807297004 +0.07, 1000000, 0.8530977807297004 0.08, 1000000, 0.8344224561281671 0.09, 1000000, 0.8162730721263407 0.1, 1000000, 0.798633693358723 @@ -19,13 +19,13 @@ 0.13, 1000000, 0.7486234889866461 0.14, 1000000, 0.7328743482797107 0.15, 1000000, 0.7175625891924777 -0.16999999999999998, 1000000, 0.6881987762208012 +0.17, 1000000, 0.6881987762208012 0.19, 1000000, 0.6604311671564058 0.21, 1000000, 0.6341650041820726 -0.22999999999999998, 1000000, 0.6093112868361505 +0.23, 1000000, 0.6093112868361505 0.25, 1000000, 0.5857864256253713 0.27, 1000000, 0.5635119158156667 -0.29000000000000004, 1000000, 0.5424140302842413 +0.29, 1000000, 0.5424140302842413 0.33, 1000000, 0.5034753937446597 0.38, 1000000, 0.46027376814120946 0.45, 1000000, 0.4085047499499631 @@ -42,7 +42,7 @@ 0.05, 3000000, 0.8558569698829943 0.055, 3000000, 0.8434078212719552 0.06, 3000000, 0.8312683250725197 -0.06999999999999999, 3000000, 0.807877378825014 +0.07, 3000000, 0.807877378825014 0.08, 3000000, 0.7856064028886043 0.09, 3000000, 0.7643837952898744 0.1, 3000000, 0.7441435217531716 @@ -51,13 +51,13 @@ 0.13, 3000000, 0.6887296985485214 0.14, 3000000, 0.6718532504643022 0.15, 3000000, 0.6556965937888862 -0.16999999999999998, 3000000, 0.6253793405724426 +0.17, 3000000, 0.6253793405724426 0.19, 3000000, 0.5974793481666101 0.21, 3000000, 0.5717379151919024 -0.22999999999999998, 3000000, 0.5479300715946065 +0.23, 3000000, 0.5479300715946065 0.25, 3000000, 0.525859486019173 0.27, 3000000, 0.5053542362122028 -0.29000000000000004, 3000000, 0.48626328167670074 +0.29, 3000000, 0.48626328167670074 0.33, 3000000, 0.4518072556048739 0.38, 3000000, 0.41462555784558464 0.45, 3000000, 0.371254010859421 @@ -74,7 +74,7 @@ 0.05, 10000000, 0.8685429921113414 0.055, 10000000, 0.8572024359670631 0.06, 10000000, 0.8461427935458878 -0.06999999999999999, 10000000, 0.8248265680621683 +0.07, 10000000, 0.8248265680621683 0.08, 10000000, 0.8045194270172396 0.09, 10000000, 0.7851530975272665 0.1, 10000000, 0.766665218442354 @@ -83,13 +83,13 @@ 0.13, 10000000, 0.7159249025906607 0.14, 10000000, 0.7004253506676488 0.15, 10000000, 0.68556193437231 -0.16999999999999998, 10000000, 0.6575961226208655 +0.17, 10000000, 0.6575961226208655 0.19, 10000000, 0.631759681239449 0.21, 10000000, 0.6078227259058706 -0.22999999999999998, 10000000, 0.5855868704094357 +0.23, 10000000, 0.5855868704094357 0.25, 10000000, 0.5648800673771895 0.27, 10000000, 0.5455524222086705 -0.29000000000000004, 10000000, 0.5274727741119862 +0.29, 10000000, 0.5274727741119862 0.33, 10000000, 0.49461012289361284 0.38, 10000000, 0.4587658967980477 0.45, 10000000, 0.41636482361397587 @@ -106,7 +106,7 @@ 0.05, 90000000, 0.8694439877186144 0.055, 90000000, 0.8582313943058512 0.06, 90000000, 0.8473030868055568 -0.06999999999999999, 90000000, 0.8262572704372576 +0.07, 90000000, 0.8262572704372576 0.08, 90000000, 0.8062274705566613 0.09, 90000000, 0.7871420372030031 0.1, 90000000, 0.7689358898389307 @@ -115,13 +115,13 @@ 0.13, 90000000, 0.719026126689054 0.14, 90000000, 0.7037939098015373 0.15, 90000000, 0.6891914824733962 -0.16999999999999998, 90000000, 0.6617262928282552 +0.17, 90000000, 0.6617262928282552 0.19, 90000000, 0.6363596297698088 0.21, 90000000, 0.6128602937515535 -0.22999999999999998, 90000000, 0.5910297971598633 +0.23, 90000000, 0.5910297971598633 0.25, 90000000, 0.5706967827806866 0.27, 90000000, 0.5517125463928281 -0.29000000000000004, 90000000, 0.5339474169852891 +0.29, 90000000, 0.5339474169852891 0.33, 90000000, 0.5016338055689489 0.38, 90000000, 0.46634353868138156 0.45, 90000000, 0.4245158057296602 diff --git a/protocol/test/Gauge.test.js b/protocol/test/Gauge.test.js index 535b5123d5..99c0fd63e5 100644 --- a/protocol/test/Gauge.test.js +++ b/protocol/test/Gauge.test.js @@ -309,7 +309,7 @@ describe('Gauge', function () { await this.fertilizer.connect(owner).setPenaltyParams(recap, to6('1000')) }) - it('getters', async function () { + i('getters', async function () { // issue unripe such that unripe supply > 10m. await this.unripeLP.mint(ownerAddress, to6('10000000')) await this.unripeBean.mint(ownerAddress, to6('10000000')) @@ -324,12 +324,17 @@ describe('Gauge', function () { // urLP supply * 0.1% recapitalization * (100-10%) = 0.9% BEANETHLP locked. // 1m beans underlay all beanETHLP tokens. // 1m * 0.9% = 900 beans locked. - expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(to6('436.332105')) - expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(to6('436.332105')) - expect(await this.unripe.getLockedBeans()).to.be.eq(to6('872.66421')) + + const locked = await this.unripe.getLockedBeansUnderlyingUnripeBean(); + console.log("locked", locked.toString()); + + + expect(await this.unripe.getLockedBeansUnderlyingUnripeBean()).to.be.eq(to6('766.665218')) + expect(await this.unripe.getLockedBeansUnderlyingUnripeLP()).to.be.eq(to6('766.665219')) + expect(await this.unripe.getLockedBeans()).to.be.eq(to6('1533.330437')) expect( await this.seasonGetters.getLiquidityToSupplyRatio() - ).to.be.eq(to18('1.000873426417975035')) + ).to.be.eq(to18('1.001535685149781809')) }) diff --git a/protocol/test/LockedBeansMainnet.test.js b/protocol/test/LockedBeansMainnet.test.js index 360efd05fb..25ac5af6b8 100644 --- a/protocol/test/LockedBeansMainnet.test.js +++ b/protocol/test/LockedBeansMainnet.test.js @@ -168,15 +168,16 @@ describe('LockedBeansMainnet', function () { }) // check underlying locked beans and locked LP: this.unripe = await ethers.getContractAt('MockUnripeFacet', BEANSTALK) - expect(await this.unripe.getLegacyLockedUnderlyingBean()).to.eq(to6('16778637.205350')) - expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within(to18('158853'),to18('158855')) + expect(await this.unripe.getLegacyLockedUnderlyingBean()).to.eq(to6('22034476.333100')) + // expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within(to18('158853'),to18('158855')) + expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within(to18('208398'),to18('208400')) // deploy misc. improvements bip await bipMiscellaneousImprovements(true, undefined, false) // check underlying locked beans and locked LP: - expect(await this.beanstalk.getLockedBeansUnderlyingUnripeBean()).to.eq(to6('3650664.793864')) - expect(await this.beanstalk.getLockedBeansUnderlyingUnripeLP()).to.be.within(to18('0.000001810930253916'),to18('0.000002010930253916')) + expect(await this.beanstalk.getLockedBeansUnderlyingUnripeBean()).to.eq(to6('14978575.114249')) + expect(await this.beanstalk.getLockedBeansUnderlyingUnripeLP()).to.be.within('7668288289687','7868288289687') }) }) }) \ No newline at end of file From 591ac6e6721ac28b97b7b7df65862160cc8cff31 Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Wed, 17 Jul 2024 12:14:10 +0200 Subject: [PATCH 69/86] Update extreme weather test --- protocol/test/Weather.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/protocol/test/Weather.test.js b/protocol/test/Weather.test.js index f276d2fb61..08ed6792f8 100644 --- a/protocol/test/Weather.test.js +++ b/protocol/test/Weather.test.js @@ -139,7 +139,7 @@ describe('Complex Weather', function () { }) // note: podrate is exremely low. - describe("Extreme Weather", async function () { + describe.only("Extreme Weather", async function () { before(async function () { await this.season.setLastDSoilE('100000'); await this.bean.mint(userAddress, '1000000000') @@ -185,7 +185,7 @@ describe('Complex Weather', function () { await this.season.setNextSowTimeE('1000') await this.season.calcCaseIdE(ethers.utils.parseEther('1'), '1'); const weather = await this.seasonGetter.weather(); - expect(weather.t).to.equal(7) + expect(weather.t).to.equal(9) expect(weather.thisSowTime).to.equal(parseInt(MAX_UINT32)) expect(weather.lastSowTime).to.equal(1000) }) @@ -195,7 +195,7 @@ describe('Complex Weather', function () { await this.season.setNextSowTimeE('1000') await this.season.calcCaseIdE(ethers.utils.parseEther('1'), '1'); const weather = await this.seasonGetter.weather(); - expect(weather.t).to.equal(7) + expect(weather.t).to.equal(9) expect(weather.thisSowTime).to.equal(parseInt(MAX_UINT32)) expect(weather.lastSowTime).to.equal(1000) }) @@ -205,7 +205,7 @@ describe('Complex Weather', function () { await this.season.setNextSowTimeE('1000') await this.season.calcCaseIdE(ethers.utils.parseEther('1'), '1'); const weather = await this.seasonGetter.weather(); - expect(weather.t).to.equal(9) + expect(weather.t).to.equal(10) expect(weather.thisSowTime).to.equal(parseInt(MAX_UINT32)) expect(weather.lastSowTime).to.equal(1000) }) @@ -215,7 +215,7 @@ describe('Complex Weather', function () { await this.season.setNextSowTimeE(MAX_UINT32) await this.season.calcCaseIdE(ethers.utils.parseEther('1'), '1'); const weather = await this.seasonGetter.weather(); - expect(weather.t).to.equal(7) + expect(weather.t).to.equal(10) expect(weather.thisSowTime).to.equal(parseInt(MAX_UINT32)) expect(weather.lastSowTime).to.equal(parseInt(MAX_UINT32)) }) From c04db80bc206c298db96ae0c81ea84b988849dee Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Wed, 17 Jul 2024 12:59:15 +0200 Subject: [PATCH 70/86] Fix bip script --- protocol/scripts/bips.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index 80445abbbb..051705682d 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -273,6 +273,9 @@ async function bipSeedGauge(mock = true, account = undefined, verbose = true) { ], 'SiloFacet': [ 'LibSilo' + ], + 'EnrootFacet': [ + 'LibSilo' ] }, bip: false, From a08b0912459542f154b2a330661f74b8fa74f02d Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 18 Jul 2024 13:45:49 +0300 Subject: [PATCH 71/86] Add init fert upgrade script and fert uri mainnet test --- .../init/InitBipMiscImprovements.sol | 35 +++++++++++++ protocol/scripts/bips.js | 1 + protocol/test/FertUpgradeMainnet.test.js | 52 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol create mode 100644 protocol/test/FertUpgradeMainnet.test.js diff --git a/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol b/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol new file mode 100644 index 0000000000..404d70873f --- /dev/null +++ b/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol @@ -0,0 +1,35 @@ +/* + SPDX-License-Identifier: MIT +*/ + +pragma solidity ^0.7.6; +pragma experimental ABIEncoderV2; + +import {AppStorage} from "../AppStorage.sol"; +import "../../C.sol"; +import "../../tokens/Fertilizer/Fertilizer.sol"; + +/** + * @author deadmanwalking + * @title InitBipMiscImprovements updates the Fertilizer implementation + * to use a decentralized uri +**/ + +contract InitBipMiscImprovements { + + AppStorage internal s; + + function init() external { + + // deploy new Fertilizer implementation + Fertilizer fertilizer = new Fertilizer(); + // get the address of the new Fertilizer implementation + address fertilizerImplementation = address(fertilizer); + + // upgrade to new Fertilizer implementation + C.fertilizerAdmin().upgrade( + C.fertilizerAddress(), + fertilizerImplementation + ); + } +} diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index 051705682d..459f2d742a 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -360,6 +360,7 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve await upgradeWithNewFacets({ diamondAddress: BEANSTALK, + initFacetName: "InitBipMiscImprovements", facetNames: [ "UnripeFacet", "ConvertFacet", diff --git a/protocol/test/FertUpgradeMainnet.test.js b/protocol/test/FertUpgradeMainnet.test.js new file mode 100644 index 0000000000..a0058f549b --- /dev/null +++ b/protocol/test/FertUpgradeMainnet.test.js @@ -0,0 +1,52 @@ +const { bipMiscellaneousImprovements } = require('../scripts/bips.js') +const { BEANSTALK, FERTILIZER } = require('./utils/constants.js') +const { assert } = require('chai') +const { takeSnapshot, revertToSnapshot } = require("./utils/snapshot.js"); + + +describe('Fert Upgrade with on-chain metadata', function () { + + before(async function () { + try { + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: process.env.FORKING_RPC, + blockNumber: 20290696 //a random semi-recent block after the seed gauge was deployed + }, + }, + ], + }); + } catch(error) { + console.log('forking error in FertUpgrade'); + console.log(error); + return + } + // fert contract + this.fert = await ethers.getContractAt('Fertilizer', FERTILIZER) + // check for old uri + const nextFertid = await this.fert.getMintId() + const uri = await this.fert.uri(nextFertid) + assert.equal(uri, 'https://fert.bean.money/1540802') + await bipMiscellaneousImprovements(); + }) + + it("gets the new fert uri", async function () { + this.fert = await ethers.getContractAt('Fertilizer', FERTILIZER) + const nextFertid = await this.fert.getMintId() + const uri = await this.fert.uri(nextFertid) + const onChainUri = "data:application/json;base64,eyJuYW1lIjogIkZlcnRpbGl6ZXIgLSAxNTQwODAyIiwgImV4dGVybmFsX3VybCI6ICJodHRwczovL2ZlcnQuYmVhbi5tb25leS8xNTQwODAyLmh0bWwiLCAiZGVzY3JpcHRpb24iOiAiQSB0cnVzdHkgY29uc3RpdHVlbnQgb2YgYW55IEZhcm1lcnMgdG9vbGJveCwgRVJDLTExNTUgRkVSVCBoYXMgYmVlbiBrbm93biB0byBzcHVyIG5ldyBncm93dGggb24gc2VlbWluZ2x5IGRlYWQgZmFybXMuIE9uY2UgcHVyY2hhc2VkIGFuZCBkZXBsb3llZCBpbnRvIGZlcnRpbGUgZ3JvdW5kIGJ5IEZhcm1lcnMsIEZlcnRpbGl6ZXIgZ2VuZXJhdGVzIG5ldyBTcHJvdXRzOiBmdXR1cmUgQmVhbnMgeWV0IHRvIGJlIHJlcGFpZCBieSBCZWFuc3RhbGsgaW4gZXhjaGFuZ2UgZm9yIGRvaW5nIHRoZSB3b3JrIG9mIFJlcGxhbnRpbmcgdGhlIHByb3RvY29sLiIsICJpbWFnZSI6ICJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUIzYVdSMGFEMGlNamswSWlCb1pXbG5hSFE5SWpVeE1pSWdkbWxsZDBKdmVEMGlNQ0F3SURJNU5DQTFNVElpSUdacGJHdzlJbTV2Ym1VaUlIaHRiRzV6UFNKb2RIUndPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1EQXdMM04yWnlJZ2VHMXNibk02ZUd4cGJtczlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5MekU1T1RrdmVHeHBibXNpUGp4d1lYUm9JR1E5SWsweE5qUXVORGNnTXpJM0xqSTBNU0F5T0M0Mk1qVWdOREExTGpjMk9Hd3RMamczT0MweU1qRXVOVFV4SURFek5TNDRORGt0TnpndU5UVTVMamczTkNBeU1qRXVOVGd6V2lJZ1ptbHNiRDBpSXpORVFVRTBOeUl2UGp4d1lYUm9JR1E5SW0weE1UZ3VNRFU1SURNMU5DNHdOemN0TkRFdU1UQXlJREl6TGpjME5pMHVPRGMwTFRJeU1TNDFOVEVnTkRFdU1UQXhMVEl6TGpjM09DNDROelVnTWpJeExqVTRNMW9pSUdacGJHdzlJaU16UkVGQk5EY2lMejQ4Y0dGMGFDQmtQU0p0TWpZdU9ESTFJREU0TkM0eU5ESXVPRGNnTWpJeExqVTJOeUE1TXk0ek5qY2dOVFF1TXpNNUxTNDROekV0TWpJeExqVTJOQzA1TXk0ek5qWXROVFF1TXpReVdtMHhNell1TkRNeUxUYzRMakkyTWk0NE56RWdNakl4TGpVMk9DQTVNeTR6TmpjZ05UUXVNek00TFM0NE56RXRNakl4TGpVMk5DMDVNeTR6TmpjdE5UUXVNelF5V2lJZ1ptbHNiRDBpSXpORVFqVTBNaUl2UGp4d1lYUm9JR1E5SWswM05pNDJNelFnTVRZeUxqa3hOU0F5TVRJZ09EUXVNVE16YkRRMExqQXpOQ0EzTlM0NU1Ea3RNVE0xTGpnME1pQTNPQzQxTkRRdE5ETXVOVFUzTFRjMUxqWTNNVm9pSUdacGJHdzlJaU00TVVRMk56SWlMejQ4Y0dGMGFDQmtQU0p0TVRJMExqazJOaUF4TXpRdU9UY2dOREF1TmpJMExUSTBMakF3TVNBME5DNHdNekVnTnpVdU9UQTJMVFF4TGpBNU9DQXlNeTQzTmpVdE5ETXVOVFUzTFRjMUxqWTNXaUlnWm1sc2JEMGlJelEyUWprMU5TSXZQanh3WVhSb0lHUTlJbTB5TVRJdU1USTFJRFEzTGpreE9DMHVNVEUySURNMkxqSXlPQzB4TXpVdU16azBJRGM0TGpjMk5pNHhNVFl0TXpZdU1UZGpNQzB5TGpBek1pMHhMak01TFRRdU5ERXpMVE11TVRNdE5TNDBOVGN0TGpnM0xTNDFNak10TVM0Mk9DMHVOVEl6TFRJdU1qWXhMUzR5TXpOc01UTTFMak01TkMwM09DNDNOalpqTGpVNExTNHpORGtnTVM0ek16SXRMakk1SURJdU1qQXpMakl6TXlBeExqY3pOaTQ1T0RrZ015NHhPRGdnTXk0ME1qVWdNeTR4T0RnZ05TNDBXaUlnWm1sc2JEMGlJelpFUTBJMk1DSXZQanh3WVhSb0lHUTlJbTB4TmpVdU56RXpJRGMwTGpjMU1pMHVNVEUySURNMkxqSXlPQzAwTUM0Mk5TQXlNeTQ1T0RndU1URTJMVE0yTGpFM1l6QXRNaTR3TXpJdE1TNHpPUzAwTGpReE15MHpMakV5T1MwMUxqUTFOeTB1T0RjeUxTNDFNak10TVM0Mk9ERXRMalV5TXkweUxqSTJNaTB1TWpNeWJEUXdMalkxTFRJekxqazRPV011TlRndExqTTBPU0F4TGpNek1pMHVNamtnTWk0eU1ETXVNak16SURFdU56TTVMams0TmlBekxqRTRPQ0F6TGpReU5TQXpMakU0T0NBMUxqUmFJaUJtYVd4c1BTSWpOREpCT0RSRElpOCtQSEJoZEdnZ1pEMGlUVGN6TGpVM09TQXhNakV1TWprNFl6RXVOek01SURFdU1EQTFJRE11TVRZeUlETXVOREl5SURNdU1UVTVJRFV1TkRJMWJDMHVNVEEwSURNMkxqRTVNeUEwTXk0MU5UY2dOelV1TmpZM0xUa3pMak0yTmkwMU5DNHpNemtnTkRNdU5USXhMVEkxTGpBeE9DNHhNRE10TXpZdU1UUXhZeTR3TURRdE1pQXhMak01TFRJdU56azFJRE11TVRNdE1TNDNPRGRhSWlCbWFXeHNQU0lqTWtNNVFUSkRJaTgrUEhCaGRHZ2daRDBpVFRFd055NDROemtnTWpJMkxqYzJOaUF6Tmk0Mk1pQXhPRFV1TlRZMWJETTFMamMwTWkweU1DNHpPVFVnTVRFdU5ESTRJREU1TGpjNU5DQXlOQzR3T0RrZ05ERXVPREF5V2lJZ1ptbHNiRDBpSXpaRVEwSTJNQ0l2UGp4d1lYUm9JR1E5SW0wNE1TNHpORGdnTVRnd0xqY3pNUzAwTkM0M01qZ2dOQzQ0TXpRZ016VXVOelF5TFRJd0xqTTVOU0E0TGprNE5pQXhOUzQxTmpGYUlpQm1hV3hzUFNJak9ERkVOamN5SWk4K0lDQThjR0YwYUNCa1BTSk5PVFV1TkRreklESXdPUzR5TXpkakxUa3VORFEzSURJdU9UWTJMVEUzTGpnME5TQXhNQzQyTXpjdE1qRXVOaklnTWpFdU5UVXlMUzQwT1RjZ01TNDFPRGt0TWk0Mk56Z2dNUzQxT0RrdE15NHlOeklnTUMwekxqSTNNaTB4TUM0eU15MHhNUzQwTURVdE1UZ3VNamMyTFRJeExqVXlMVEl4TGpVMU1pMHhMamM0TkMwdU5UazRMVEV1TnpnMExUSXVOemd5SURBdE15NHpOemNnTVRBdU1URTFMVE11TXpFeUlERTRMakUzTkMweE1TNDFNRFlnTWpFdU5USXRNakV1TlRVeUxqVTVOQzB4TGpZNE9TQXlMamMzT0MweExqWTRPU0F6TGpJM01pQXdJRE11TnpZNElERXdMalk0T1NBeE1TNDFOak1nTVRndU1UazFJREl4TGpZeUlESXhMalUxTWlBeExqWTROeTQxT1RVZ01TNDJPRGNnTWk0M056a2dNQ0F6TGpNM04xb2lJR1pwYkd3OUlpTm1abVlpTHo0OGNHRjBhQ0JrUFNKdE1qVTJMamc1T0NBek9ERXVOakE1TFRFek5TNDRORFlnTnpndU5USTNMUzQ0TnpjdE1qSXhMalUxTVNBeE16VXVPRFE1TFRjNExqVTJMamczTkNBeU1qRXVOVGcwV2lJZ1ptbHNiRDBpSXpaRVEwSTJNQ0l2UGp4d1lYUm9JR1E5SW0weU1UQXVORGcySURRd09DNDBORFV0TkRFdU1UQXhJREl6TGpjME5TMHVPRGMxTFRJeU1TNDFOVEVnTkRFdU1UQXlMVEl6TGpjM09DNDROelFnTWpJeExqVTRORm9pSUdacGJHdzlJaU16UkVGQk5EY2lMejQ4Y0dGMGFDQmtQU0p0TWpRd0xqa3dNU0F6TmpRdU9UUTVMVEV3TkM0ME1EY2dOakF1TXpnM0xTNHpNak10TVRVM0xqUTNOeUF4TURRdU5EQTRMVFl3TGpNMU1TNHpNaklnTVRVM0xqUTBNVm9pSUdacGJHdzlJaU5tWm1ZaUx6NDhjR0YwYUNCa1BTSk5NVGsxTGpjNE9TQXlOamd1TURJMVl6SXpMakV6TnkwMkxqY3hOQ0F6Tmk0NE56VWdNVEF1TmpNeElETXlMak13TmlBek5TNHlNek10TkM0d01pQXlNUzQyTlRJdE1qRXVNelV5SURReUxqZzBOUzB6T1M0M05qa2dORGt1T0RJeExURTVMakUzTVNBM0xqSTJMVE0xTGpjeE55MHlMakkyT0Mwek5pNHlPVGN0TWpNdU9UWTJMUzQyTmpVdE1qUXVPVEl5SURFNUxqUXhNeTAxTkM0d01qRWdORE11TnpZdE5qRXVNRGc0V2lJZ1ptbHNiRDBpSXpRMlFqazFOU0l2UGp4d1lYUm9JR1E5SW0weU1EWXVOREUzSURJM05TNDJNVFV0TWpndU1EZ2dOek11TlRjM2N5MHlOQzQxTmprdE16VXVNemszSURJNExqQTRMVGN6TGpVM04xcHRMVEl6TGpBeU55QTJPQzR6TmpJZ01Ua3VOVFl4TFRVd0xqa3hObk15TXk0NE16RWdNVGN1TVRnNUxURTVMalUyTVNBMU1DNDVNVFphSWlCbWFXeHNQU0lqWm1abUlpOCtQSFJsZUhRZ1ptOXVkQzFtWVcxcGJIazlJbk5oYm5NdGMyVnlhV1lpSUdadmJuUXRjMmw2WlQwaU1qQWlJSGc5SWpJd0lpQjVQU0kwT1RBaUlHWnBiR3c5SW1Kc1lXTnJJaUErUEhSemNHRnVJR1I1UFNJd0lpQjRQU0l5TUNJK01DNHdNQ0JDVUVZZ1VtVnRZV2x1YVc1bklEd3ZkSE53WVc0K1BDOTBaWGgwUGp3dmMzWm5QZz09IiwgImF0dHJpYnV0ZXMiOiBbeyAidHJhaXRfdHlwZSI6ICJCUEYgUmVtYWluaW5nIiwiZGlzcGxheV90eXBlIjogImJvb3N0X251bWJlciIsInZhbHVlIjogMC4wMCB9XX0=" + assert.equal(uri, onChainUri) + }) + + it("keeps the same fert owner", async function () { + // fert contract + this.fert = await ethers.getContractAt('Fertilizer', FERTILIZER) + // fert beanstalk facet + const owner = await this.fert.owner() + assert.equal(owner, BEANSTALK) + }) + +}) \ No newline at end of file From 8b2fe9c29ef2ee267bb091f3682209f0a6ac797e Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 18 Jul 2024 13:47:18 +0300 Subject: [PATCH 72/86] remove appstorage from init script --- protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol b/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol index 404d70873f..71ab22cb10 100644 --- a/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol +++ b/protocol/contracts/beanstalk/init/InitBipMiscImprovements.sol @@ -5,7 +5,6 @@ pragma solidity ^0.7.6; pragma experimental ABIEncoderV2; -import {AppStorage} from "../AppStorage.sol"; import "../../C.sol"; import "../../tokens/Fertilizer/Fertilizer.sol"; @@ -17,8 +16,6 @@ import "../../tokens/Fertilizer/Fertilizer.sol"; contract InitBipMiscImprovements { - AppStorage internal s; - function init() external { // deploy new Fertilizer implementation From 29970cf833a13ca3aa3d62ae51732b9db7ab1472 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Thu, 18 Jul 2024 15:46:30 +0300 Subject: [PATCH 73/86] Update bpf remaining calculation --- .../tokens/Fertilizer/Internalizer.sol | 6 ++--- protocol/test/FertUpgradeMainnet.test.js | 4 ++-- protocol/test/Fertilizer.test.js | 23 ++++++++++--------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/protocol/contracts/tokens/Fertilizer/Internalizer.sol b/protocol/contracts/tokens/Fertilizer/Internalizer.sol index 5611971bd9..f0b25400df 100644 --- a/protocol/contracts/tokens/Fertilizer/Internalizer.sol +++ b/protocol/contracts/tokens/Fertilizer/Internalizer.sol @@ -102,14 +102,14 @@ contract Internalizer is OwnableUpgradeable, ReentrancyGuardUpgradeable, Fertili /** * @notice Returns the beans per fertilizer remaining for a given fertilizer Id. * @param id - the id of the fertilizer - * Formula: bpfRemaining = s.bpf - id + * Formula: bpfRemaining = id - s.bpf * Calculated here to avoid uint underflow * Solidity 0.8.0 has underflow protection and the tx would revert but we are using 0.7.6 */ function calculateBpfRemaining(uint256 id) internal view returns (uint128) { // make sure it does not underflow - if (IBeanstalk(BEANSTALK).beansPerFertilizer() >= uint128(id)) { - return IBeanstalk(BEANSTALK).beansPerFertilizer() - uint128(id); + if (uint128(id) >= IBeanstalk(BEANSTALK).beansPerFertilizer()) { + return uint128(id) - IBeanstalk(BEANSTALK).beansPerFertilizer() ; } else { return 0; } diff --git a/protocol/test/FertUpgradeMainnet.test.js b/protocol/test/FertUpgradeMainnet.test.js index a0058f549b..1d313c8a8b 100644 --- a/protocol/test/FertUpgradeMainnet.test.js +++ b/protocol/test/FertUpgradeMainnet.test.js @@ -14,7 +14,7 @@ describe('Fert Upgrade with on-chain metadata', function () { { forking: { jsonRpcUrl: process.env.FORKING_RPC, - blockNumber: 20290696 //a random semi-recent block after the seed gauge was deployed + blockNumber: 20333299 //a random semi-recent block after the seed gauge was deployed }, }, ], @@ -37,7 +37,7 @@ describe('Fert Upgrade with on-chain metadata', function () { this.fert = await ethers.getContractAt('Fertilizer', FERTILIZER) const nextFertid = await this.fert.getMintId() const uri = await this.fert.uri(nextFertid) - const onChainUri = "data:application/json;base64,eyJuYW1lIjogIkZlcnRpbGl6ZXIgLSAxNTQwODAyIiwgImV4dGVybmFsX3VybCI6ICJodHRwczovL2ZlcnQuYmVhbi5tb25leS8xNTQwODAyLmh0bWwiLCAiZGVzY3JpcHRpb24iOiAiQSB0cnVzdHkgY29uc3RpdHVlbnQgb2YgYW55IEZhcm1lcnMgdG9vbGJveCwgRVJDLTExNTUgRkVSVCBoYXMgYmVlbiBrbm93biB0byBzcHVyIG5ldyBncm93dGggb24gc2VlbWluZ2x5IGRlYWQgZmFybXMuIE9uY2UgcHVyY2hhc2VkIGFuZCBkZXBsb3llZCBpbnRvIGZlcnRpbGUgZ3JvdW5kIGJ5IEZhcm1lcnMsIEZlcnRpbGl6ZXIgZ2VuZXJhdGVzIG5ldyBTcHJvdXRzOiBmdXR1cmUgQmVhbnMgeWV0IHRvIGJlIHJlcGFpZCBieSBCZWFuc3RhbGsgaW4gZXhjaGFuZ2UgZm9yIGRvaW5nIHRoZSB3b3JrIG9mIFJlcGxhbnRpbmcgdGhlIHByb3RvY29sLiIsICJpbWFnZSI6ICJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUIzYVdSMGFEMGlNamswSWlCb1pXbG5hSFE5SWpVeE1pSWdkbWxsZDBKdmVEMGlNQ0F3SURJNU5DQTFNVElpSUdacGJHdzlJbTV2Ym1VaUlIaHRiRzV6UFNKb2RIUndPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1EQXdMM04yWnlJZ2VHMXNibk02ZUd4cGJtczlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5MekU1T1RrdmVHeHBibXNpUGp4d1lYUm9JR1E5SWsweE5qUXVORGNnTXpJM0xqSTBNU0F5T0M0Mk1qVWdOREExTGpjMk9Hd3RMamczT0MweU1qRXVOVFV4SURFek5TNDRORGt0TnpndU5UVTVMamczTkNBeU1qRXVOVGd6V2lJZ1ptbHNiRDBpSXpORVFVRTBOeUl2UGp4d1lYUm9JR1E5SW0weE1UZ3VNRFU1SURNMU5DNHdOemN0TkRFdU1UQXlJREl6TGpjME5pMHVPRGMwTFRJeU1TNDFOVEVnTkRFdU1UQXhMVEl6TGpjM09DNDROelVnTWpJeExqVTRNMW9pSUdacGJHdzlJaU16UkVGQk5EY2lMejQ4Y0dGMGFDQmtQU0p0TWpZdU9ESTFJREU0TkM0eU5ESXVPRGNnTWpJeExqVTJOeUE1TXk0ek5qY2dOVFF1TXpNNUxTNDROekV0TWpJeExqVTJOQzA1TXk0ek5qWXROVFF1TXpReVdtMHhNell1TkRNeUxUYzRMakkyTWk0NE56RWdNakl4TGpVMk9DQTVNeTR6TmpjZ05UUXVNek00TFM0NE56RXRNakl4TGpVMk5DMDVNeTR6TmpjdE5UUXVNelF5V2lJZ1ptbHNiRDBpSXpORVFqVTBNaUl2UGp4d1lYUm9JR1E5SWswM05pNDJNelFnTVRZeUxqa3hOU0F5TVRJZ09EUXVNVE16YkRRMExqQXpOQ0EzTlM0NU1Ea3RNVE0xTGpnME1pQTNPQzQxTkRRdE5ETXVOVFUzTFRjMUxqWTNNVm9pSUdacGJHdzlJaU00TVVRMk56SWlMejQ4Y0dGMGFDQmtQU0p0TVRJMExqazJOaUF4TXpRdU9UY2dOREF1TmpJMExUSTBMakF3TVNBME5DNHdNekVnTnpVdU9UQTJMVFF4TGpBNU9DQXlNeTQzTmpVdE5ETXVOVFUzTFRjMUxqWTNXaUlnWm1sc2JEMGlJelEyUWprMU5TSXZQanh3WVhSb0lHUTlJbTB5TVRJdU1USTFJRFEzTGpreE9DMHVNVEUySURNMkxqSXlPQzB4TXpVdU16azBJRGM0TGpjMk5pNHhNVFl0TXpZdU1UZGpNQzB5TGpBek1pMHhMak01TFRRdU5ERXpMVE11TVRNdE5TNDBOVGN0TGpnM0xTNDFNak10TVM0Mk9DMHVOVEl6TFRJdU1qWXhMUzR5TXpOc01UTTFMak01TkMwM09DNDNOalpqTGpVNExTNHpORGtnTVM0ek16SXRMakk1SURJdU1qQXpMakl6TXlBeExqY3pOaTQ1T0RrZ015NHhPRGdnTXk0ME1qVWdNeTR4T0RnZ05TNDBXaUlnWm1sc2JEMGlJelpFUTBJMk1DSXZQanh3WVhSb0lHUTlJbTB4TmpVdU56RXpJRGMwTGpjMU1pMHVNVEUySURNMkxqSXlPQzAwTUM0Mk5TQXlNeTQ1T0RndU1URTJMVE0yTGpFM1l6QXRNaTR3TXpJdE1TNHpPUzAwTGpReE15MHpMakV5T1MwMUxqUTFOeTB1T0RjeUxTNDFNak10TVM0Mk9ERXRMalV5TXkweUxqSTJNaTB1TWpNeWJEUXdMalkxTFRJekxqazRPV011TlRndExqTTBPU0F4TGpNek1pMHVNamtnTWk0eU1ETXVNak16SURFdU56TTVMams0TmlBekxqRTRPQ0F6TGpReU5TQXpMakU0T0NBMUxqUmFJaUJtYVd4c1BTSWpOREpCT0RSRElpOCtQSEJoZEdnZ1pEMGlUVGN6TGpVM09TQXhNakV1TWprNFl6RXVOek01SURFdU1EQTFJRE11TVRZeUlETXVOREl5SURNdU1UVTVJRFV1TkRJMWJDMHVNVEEwSURNMkxqRTVNeUEwTXk0MU5UY2dOelV1TmpZM0xUa3pMak0yTmkwMU5DNHpNemtnTkRNdU5USXhMVEkxTGpBeE9DNHhNRE10TXpZdU1UUXhZeTR3TURRdE1pQXhMak01TFRJdU56azFJRE11TVRNdE1TNDNPRGRhSWlCbWFXeHNQU0lqTWtNNVFUSkRJaTgrUEhCaGRHZ2daRDBpVFRFd055NDROemtnTWpJMkxqYzJOaUF6Tmk0Mk1pQXhPRFV1TlRZMWJETTFMamMwTWkweU1DNHpPVFVnTVRFdU5ESTRJREU1TGpjNU5DQXlOQzR3T0RrZ05ERXVPREF5V2lJZ1ptbHNiRDBpSXpaRVEwSTJNQ0l2UGp4d1lYUm9JR1E5SW0wNE1TNHpORGdnTVRnd0xqY3pNUzAwTkM0M01qZ2dOQzQ0TXpRZ016VXVOelF5TFRJd0xqTTVOU0E0TGprNE5pQXhOUzQxTmpGYUlpQm1hV3hzUFNJak9ERkVOamN5SWk4K0lDQThjR0YwYUNCa1BTSk5PVFV1TkRreklESXdPUzR5TXpkakxUa3VORFEzSURJdU9UWTJMVEUzTGpnME5TQXhNQzQyTXpjdE1qRXVOaklnTWpFdU5UVXlMUzQwT1RjZ01TNDFPRGt0TWk0Mk56Z2dNUzQxT0RrdE15NHlOeklnTUMwekxqSTNNaTB4TUM0eU15MHhNUzQwTURVdE1UZ3VNamMyTFRJeExqVXlMVEl4TGpVMU1pMHhMamM0TkMwdU5UazRMVEV1TnpnMExUSXVOemd5SURBdE15NHpOemNnTVRBdU1URTFMVE11TXpFeUlERTRMakUzTkMweE1TNDFNRFlnTWpFdU5USXRNakV1TlRVeUxqVTVOQzB4TGpZNE9TQXlMamMzT0MweExqWTRPU0F6TGpJM01pQXdJRE11TnpZNElERXdMalk0T1NBeE1TNDFOak1nTVRndU1UazFJREl4TGpZeUlESXhMalUxTWlBeExqWTROeTQxT1RVZ01TNDJPRGNnTWk0M056a2dNQ0F6TGpNM04xb2lJR1pwYkd3OUlpTm1abVlpTHo0OGNHRjBhQ0JrUFNKdE1qVTJMamc1T0NBek9ERXVOakE1TFRFek5TNDRORFlnTnpndU5USTNMUzQ0TnpjdE1qSXhMalUxTVNBeE16VXVPRFE1TFRjNExqVTJMamczTkNBeU1qRXVOVGcwV2lJZ1ptbHNiRDBpSXpaRVEwSTJNQ0l2UGp4d1lYUm9JR1E5SW0weU1UQXVORGcySURRd09DNDBORFV0TkRFdU1UQXhJREl6TGpjME5TMHVPRGMxTFRJeU1TNDFOVEVnTkRFdU1UQXlMVEl6TGpjM09DNDROelFnTWpJeExqVTRORm9pSUdacGJHdzlJaU16UkVGQk5EY2lMejQ4Y0dGMGFDQmtQU0p0TWpRd0xqa3dNU0F6TmpRdU9UUTVMVEV3TkM0ME1EY2dOakF1TXpnM0xTNHpNak10TVRVM0xqUTNOeUF4TURRdU5EQTRMVFl3TGpNMU1TNHpNaklnTVRVM0xqUTBNVm9pSUdacGJHdzlJaU5tWm1ZaUx6NDhjR0YwYUNCa1BTSk5NVGsxTGpjNE9TQXlOamd1TURJMVl6SXpMakV6TnkwMkxqY3hOQ0F6Tmk0NE56VWdNVEF1TmpNeElETXlMak13TmlBek5TNHlNek10TkM0d01pQXlNUzQyTlRJdE1qRXVNelV5SURReUxqZzBOUzB6T1M0M05qa2dORGt1T0RJeExURTVMakUzTVNBM0xqSTJMVE0xTGpjeE55MHlMakkyT0Mwek5pNHlPVGN0TWpNdU9UWTJMUzQyTmpVdE1qUXVPVEl5SURFNUxqUXhNeTAxTkM0d01qRWdORE11TnpZdE5qRXVNRGc0V2lJZ1ptbHNiRDBpSXpRMlFqazFOU0l2UGp4d1lYUm9JR1E5SW0weU1EWXVOREUzSURJM05TNDJNVFV0TWpndU1EZ2dOek11TlRjM2N5MHlOQzQxTmprdE16VXVNemszSURJNExqQTRMVGN6TGpVM04xcHRMVEl6TGpBeU55QTJPQzR6TmpJZ01Ua3VOVFl4TFRVd0xqa3hObk15TXk0NE16RWdNVGN1TVRnNUxURTVMalUyTVNBMU1DNDVNVFphSWlCbWFXeHNQU0lqWm1abUlpOCtQSFJsZUhRZ1ptOXVkQzFtWVcxcGJIazlJbk5oYm5NdGMyVnlhV1lpSUdadmJuUXRjMmw2WlQwaU1qQWlJSGc5SWpJd0lpQjVQU0kwT1RBaUlHWnBiR3c5SW1Kc1lXTnJJaUErUEhSemNHRnVJR1I1UFNJd0lpQjRQU0l5TUNJK01DNHdNQ0JDVUVZZ1VtVnRZV2x1YVc1bklEd3ZkSE53WVc0K1BDOTBaWGgwUGp3dmMzWm5QZz09IiwgImF0dHJpYnV0ZXMiOiBbeyAidHJhaXRfdHlwZSI6ICJCUEYgUmVtYWluaW5nIiwiZGlzcGxheV90eXBlIjogImJvb3N0X251bWJlciIsInZhbHVlIjogMC4wMCB9XX0=" + const onChainUri = "data:application/json;base64,eyJuYW1lIjogIkZlcnRpbGl6ZXIgLSAxNTQwODAyIiwgImV4dGVybmFsX3VybCI6ICJodHRwczovL2ZlcnQuYmVhbi5tb25leS8xNTQwODAyLmh0bWwiLCAiZGVzY3JpcHRpb24iOiAiQSB0cnVzdHkgY29uc3RpdHVlbnQgb2YgYW55IEZhcm1lcnMgdG9vbGJveCwgRVJDLTExNTUgRkVSVCBoYXMgYmVlbiBrbm93biB0byBzcHVyIG5ldyBncm93dGggb24gc2VlbWluZ2x5IGRlYWQgZmFybXMuIE9uY2UgcHVyY2hhc2VkIGFuZCBkZXBsb3llZCBpbnRvIGZlcnRpbGUgZ3JvdW5kIGJ5IEZhcm1lcnMsIEZlcnRpbGl6ZXIgZ2VuZXJhdGVzIG5ldyBTcHJvdXRzOiBmdXR1cmUgQmVhbnMgeWV0IHRvIGJlIHJlcGFpZCBieSBCZWFuc3RhbGsgaW4gZXhjaGFuZ2UgZm9yIGRvaW5nIHRoZSB3b3JrIG9mIFJlcGxhbnRpbmcgdGhlIHByb3RvY29sLiIsICJpbWFnZSI6ICJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUIzYVdSMGFEMGlNamswSWlCb1pXbG5hSFE5SWpVeE1pSWdkbWxsZDBKdmVEMGlNQ0F3SURJNU5DQTFNVElpSUdacGJHdzlJbTV2Ym1VaUlIaHRiRzV6UFNKb2RIUndPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1EQXdMM04yWnlJZ2VHMXNibk02ZUd4cGJtczlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5MekU1T1RrdmVHeHBibXNpUGp4d1lYUm9JR1E5SWsweE5qUXVORGNnTXpJM0xqSTBNU0F5T0M0Mk1qVWdOREExTGpjMk9Hd3RMamczT0MweU1qRXVOVFV4SURFek5TNDRORGt0TnpndU5UVTVMamczTkNBeU1qRXVOVGd6V2lJZ1ptbHNiRDBpSXpORVFVRTBOeUl2UGp4d1lYUm9JR1E5SW0weE1UZ3VNRFU1SURNMU5DNHdOemN0TkRFdU1UQXlJREl6TGpjME5pMHVPRGMwTFRJeU1TNDFOVEVnTkRFdU1UQXhMVEl6TGpjM09DNDROelVnTWpJeExqVTRNMW9pSUdacGJHdzlJaU16UkVGQk5EY2lMejQ4Y0dGMGFDQmtQU0p0TWpZdU9ESTFJREU0TkM0eU5ESXVPRGNnTWpJeExqVTJOeUE1TXk0ek5qY2dOVFF1TXpNNUxTNDROekV0TWpJeExqVTJOQzA1TXk0ek5qWXROVFF1TXpReVdtMHhNell1TkRNeUxUYzRMakkyTWk0NE56RWdNakl4TGpVMk9DQTVNeTR6TmpjZ05UUXVNek00TFM0NE56RXRNakl4TGpVMk5DMDVNeTR6TmpjdE5UUXVNelF5V2lJZ1ptbHNiRDBpSXpORVFqVTBNaUl2UGp4d1lYUm9JR1E5SWswM05pNDJNelFnTVRZeUxqa3hOU0F5TVRJZ09EUXVNVE16YkRRMExqQXpOQ0EzTlM0NU1Ea3RNVE0xTGpnME1pQTNPQzQxTkRRdE5ETXVOVFUzTFRjMUxqWTNNVm9pSUdacGJHdzlJaU00TVVRMk56SWlMejQ4Y0dGMGFDQmtQU0p0TVRJMExqazJOaUF4TXpRdU9UY2dOREF1TmpJMExUSTBMakF3TVNBME5DNHdNekVnTnpVdU9UQTJMVFF4TGpBNU9DQXlNeTQzTmpVdE5ETXVOVFUzTFRjMUxqWTNXaUlnWm1sc2JEMGlJelEyUWprMU5TSXZQanh3WVhSb0lHUTlJbTB5TVRJdU1USTFJRFEzTGpreE9DMHVNVEUySURNMkxqSXlPQzB4TXpVdU16azBJRGM0TGpjMk5pNHhNVFl0TXpZdU1UZGpNQzB5TGpBek1pMHhMak01TFRRdU5ERXpMVE11TVRNdE5TNDBOVGN0TGpnM0xTNDFNak10TVM0Mk9DMHVOVEl6TFRJdU1qWXhMUzR5TXpOc01UTTFMak01TkMwM09DNDNOalpqTGpVNExTNHpORGtnTVM0ek16SXRMakk1SURJdU1qQXpMakl6TXlBeExqY3pOaTQ1T0RrZ015NHhPRGdnTXk0ME1qVWdNeTR4T0RnZ05TNDBXaUlnWm1sc2JEMGlJelpFUTBJMk1DSXZQanh3WVhSb0lHUTlJbTB4TmpVdU56RXpJRGMwTGpjMU1pMHVNVEUySURNMkxqSXlPQzAwTUM0Mk5TQXlNeTQ1T0RndU1URTJMVE0yTGpFM1l6QXRNaTR3TXpJdE1TNHpPUzAwTGpReE15MHpMakV5T1MwMUxqUTFOeTB1T0RjeUxTNDFNak10TVM0Mk9ERXRMalV5TXkweUxqSTJNaTB1TWpNeWJEUXdMalkxTFRJekxqazRPV011TlRndExqTTBPU0F4TGpNek1pMHVNamtnTWk0eU1ETXVNak16SURFdU56TTVMams0TmlBekxqRTRPQ0F6TGpReU5TQXpMakU0T0NBMUxqUmFJaUJtYVd4c1BTSWpOREpCT0RSRElpOCtQSEJoZEdnZ1pEMGlUVGN6TGpVM09TQXhNakV1TWprNFl6RXVOek01SURFdU1EQTFJRE11TVRZeUlETXVOREl5SURNdU1UVTVJRFV1TkRJMWJDMHVNVEEwSURNMkxqRTVNeUEwTXk0MU5UY2dOelV1TmpZM0xUa3pMak0yTmkwMU5DNHpNemtnTkRNdU5USXhMVEkxTGpBeE9DNHhNRE10TXpZdU1UUXhZeTR3TURRdE1pQXhMak01TFRJdU56azFJRE11TVRNdE1TNDNPRGRhSWlCbWFXeHNQU0lqTWtNNVFUSkRJaTgrUEhCaGRHZ2daRDBpVFRFd055NDROemtnTWpJMkxqYzJOaUF6Tmk0Mk1pQXhPRFV1TlRZMWJETTFMamMwTWkweU1DNHpPVFVnTVRFdU5ESTRJREU1TGpjNU5DQXlOQzR3T0RrZ05ERXVPREF5V2lJZ1ptbHNiRDBpSXpaRVEwSTJNQ0l2UGp4d1lYUm9JR1E5SW0wNE1TNHpORGdnTVRnd0xqY3pNUzAwTkM0M01qZ2dOQzQ0TXpRZ016VXVOelF5TFRJd0xqTTVOU0E0TGprNE5pQXhOUzQxTmpGYUlpQm1hV3hzUFNJak9ERkVOamN5SWk4K0lDQThjR0YwYUNCa1BTSk5PVFV1TkRreklESXdPUzR5TXpkakxUa3VORFEzSURJdU9UWTJMVEUzTGpnME5TQXhNQzQyTXpjdE1qRXVOaklnTWpFdU5UVXlMUzQwT1RjZ01TNDFPRGt0TWk0Mk56Z2dNUzQxT0RrdE15NHlOeklnTUMwekxqSTNNaTB4TUM0eU15MHhNUzQwTURVdE1UZ3VNamMyTFRJeExqVXlMVEl4TGpVMU1pMHhMamM0TkMwdU5UazRMVEV1TnpnMExUSXVOemd5SURBdE15NHpOemNnTVRBdU1URTFMVE11TXpFeUlERTRMakUzTkMweE1TNDFNRFlnTWpFdU5USXRNakV1TlRVeUxqVTVOQzB4TGpZNE9TQXlMamMzT0MweExqWTRPU0F6TGpJM01pQXdJRE11TnpZNElERXdMalk0T1NBeE1TNDFOak1nTVRndU1UazFJREl4TGpZeUlESXhMalUxTWlBeExqWTROeTQxT1RVZ01TNDJPRGNnTWk0M056a2dNQ0F6TGpNM04xb2lJR1pwYkd3OUlpTm1abVlpTHo0OGNHRjBhQ0JrUFNKdE1qVTJMamc1T0NBek9ERXVOakE1TFRFek5TNDRORFlnTnpndU5USTNMUzQ0TnpjdE1qSXhMalUxTVNBeE16VXVPRFE1TFRjNExqVTJMamczTkNBeU1qRXVOVGcwV2lJZ1ptbHNiRDBpSXpaRVEwSTJNQ0l2UGp4d1lYUm9JR1E5SW0weU1UQXVORGcySURRd09DNDBORFV0TkRFdU1UQXhJREl6TGpjME5TMHVPRGMxTFRJeU1TNDFOVEVnTkRFdU1UQXlMVEl6TGpjM09DNDROelFnTWpJeExqVTRORm9pSUdacGJHdzlJaU16UkVGQk5EY2lMejQ4Y0dGMGFDQmtQU0p0TWpRd0xqa3dNU0F6TmpRdU9UUTVMVEV3TkM0ME1EY2dOakF1TXpnM0xTNHpNak10TVRVM0xqUTNOeUF4TURRdU5EQTRMVFl3TGpNMU1TNHpNaklnTVRVM0xqUTBNVm9pSUdacGJHdzlJaU5tWm1ZaUx6NDhjR0YwYUNCa1BTSk5NVGsxTGpjNE9TQXlOamd1TURJMVl6SXpMakV6TnkwMkxqY3hOQ0F6Tmk0NE56VWdNVEF1TmpNeElETXlMak13TmlBek5TNHlNek10TkM0d01pQXlNUzQyTlRJdE1qRXVNelV5SURReUxqZzBOUzB6T1M0M05qa2dORGt1T0RJeExURTVMakUzTVNBM0xqSTJMVE0xTGpjeE55MHlMakkyT0Mwek5pNHlPVGN0TWpNdU9UWTJMUzQyTmpVdE1qUXVPVEl5SURFNUxqUXhNeTAxTkM0d01qRWdORE11TnpZdE5qRXVNRGc0V2lJZ1ptbHNiRDBpSXpRMlFqazFOU0l2UGp4d1lYUm9JR1E5SW0weU1EWXVOREUzSURJM05TNDJNVFV0TWpndU1EZ2dOek11TlRjM2N5MHlOQzQxTmprdE16VXVNemszSURJNExqQTRMVGN6TGpVM04xcHRMVEl6TGpBeU55QTJPQzR6TmpJZ01Ua3VOVFl4TFRVd0xqa3hObk15TXk0NE16RWdNVGN1TVRnNUxURTVMalUyTVNBMU1DNDVNVFphSWlCbWFXeHNQU0lqWm1abUlpOCtQSFJsZUhRZ1ptOXVkQzFtWVcxcGJIazlJbk5oYm5NdGMyVnlhV1lpSUdadmJuUXRjMmw2WlQwaU1qQWlJSGc5SWpJd0lpQjVQU0kwT1RBaUlHWnBiR3c5SW1Kc1lXTnJJaUErUEhSemNHRnVJR1I1UFNJd0lpQjRQU0l5TUNJK01TNHlNQ0JDVUVZZ1VtVnRZV2x1YVc1bklEd3ZkSE53WVc0K1BDOTBaWGgwUGp3dmMzWm5QZz09IiwgImF0dHJpYnV0ZXMiOiBbeyAidHJhaXRfdHlwZSI6ICJCUEYgUmVtYWluaW5nIiwiZGlzcGxheV90eXBlIjogImJvb3N0X251bWJlciIsInZhbHVlIjogMS4yMCB9XX0=" assert.equal(uri, onChainUri) }) diff --git a/protocol/test/Fertilizer.test.js b/protocol/test/Fertilizer.test.js index 47df0020b1..25228e5294 100644 --- a/protocol/test/Fertilizer.test.js +++ b/protocol/test/Fertilizer.test.js @@ -833,7 +833,7 @@ describe('Fertilize', function () { // Maths: // uint128 current season bpf = Humidity + 1000 * 1,000 // so 2500 + 1000 * 1,000 = 3500000 correct // uint128 endBpf = totalbpf (s.bpf) + current season bpf; // so 0 + 3500000 = 3500000 correct - // uint128 bpfRemaining = totalbpf (s.bpf) - id; // so 0 - 3500000 = -3500000 correct but since it is uint128 it is 340282366920938463463374607431764711456 --> loops back + // uint128 bpfRemaining = id - s.bpf; // so 3500000 - 0 = 3500000 // uint128 fertilizer id = current season bpf + totalbpf // so 3500000 + 0 = 3500000 correct // uint128 s.bpf // 0 // Humidity // 2500 @@ -853,12 +853,12 @@ describe('Fertilize', function () { // Available fert test it("returns an available fertilizer svg and stats when supply (fertilizer[id]) is 0", async function () { - // Manipulate bpf to 5000000 - // new bpfremaining for id 350001 = 5000000 - 3500001 = 1499999 - await this.fertilizer.setBpf(5000000); + // Manipulate bpf to 2000000 + // new bpfremaining for id 350001 = 3500001 - 2000000 = 1500001 + await this.fertilizer.setBpf(2000000); // This returns an available image of fert - const availableDataImage = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjk0IiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDI5NCA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0xNjQuNDcgMzI3LjI0MSAyOC42MjUgNDA1Ljc2OGwtLjg3OC0yMjEuNTUxIDEzNS44NDktNzguNTU5Ljg3NCAyMjEuNTgzWiIgZmlsbD0iIzNEQUE0NyIvPjxwYXRoIGQ9Im0xMTguMDU5IDM1NC4wNzctNDEuMTAyIDIzLjc0Ni0uODc0LTIyMS41NTEgNDEuMTAxLTIzLjc3OC44NzUgMjIxLjU4M1oiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjYuODI1IDE4NC4yNDIuODcgMjIxLjU2NyA5My4zNjcgNTQuMzM5LS44NzEtMjIxLjU2NC05My4zNjYtNTQuMzQyWm0xMzYuNDMyLTc4LjI2Mi44NzEgMjIxLjU2OCA5My4zNjcgNTQuMzM4LS44NzEtMjIxLjU2NC05My4zNjctNTQuMzQyWiIgZmlsbD0iIzNEQjU0MiIvPjxwYXRoIGQ9Ik03Ni42MzQgMTYyLjkxNSAyMTIgODQuMTMzbDQ0LjAzNCA3NS45MDktMTM1Ljg0MiA3OC41NDQtNDMuNTU3LTc1LjY3MVoiIGZpbGw9IiM4MUQ2NzIiLz48cGF0aCBkPSJtMTI0Ljk2NiAxMzQuOTcgNDAuNjI0LTI0LjAwMSA0NC4wMzEgNzUuOTA2LTQxLjA5OCAyMy43NjUtNDMuNTU3LTc1LjY3WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMTIuMTI1IDQ3LjkxOC0uMTE2IDM2LjIyOC0xMzUuMzk0IDc4Ljc2Ni4xMTYtMzYuMTdjMC0yLjAzMi0xLjM5LTQuNDEzLTMuMTMtNS40NTctLjg3LS41MjMtMS42OC0uNTIzLTIuMjYxLS4yMzNsMTM1LjM5NC03OC43NjZjLjU4LS4zNDkgMS4zMzItLjI5IDIuMjAzLjIzMyAxLjczNi45ODkgMy4xODggMy40MjUgMy4xODggNS40WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0xNjUuNzEzIDc0Ljc1Mi0uMTE2IDM2LjIyOC00MC42NSAyMy45ODguMTE2LTM2LjE3YzAtMi4wMzItMS4zOS00LjQxMy0zLjEyOS01LjQ1Ny0uODcyLS41MjMtMS42ODEtLjUyMy0yLjI2Mi0uMjMybDQwLjY1LTIzLjk4OWMuNTgtLjM0OSAxLjMzMi0uMjkgMi4yMDMuMjMzIDEuNzM5Ljk4NiAzLjE4OCAzLjQyNSAzLjE4OCA1LjRaIiBmaWxsPSIjNDJBODRDIi8+PHBhdGggZD0iTTczLjU3OSAxMjEuMjk4YzEuNzM5IDEuMDA1IDMuMTYyIDMuNDIyIDMuMTU5IDUuNDI1bC0uMTA0IDM2LjE5MyA0My41NTcgNzUuNjY3LTkzLjM2Ni01NC4zMzkgNDMuNTIxLTI1LjAxOC4xMDMtMzYuMTQxYy4wMDQtMiAxLjM5LTIuNzk1IDMuMTMtMS43ODdaIiBmaWxsPSIjMkM5QTJDIi8+PHBhdGggZD0iTTEwNy44NzkgMjI2Ljc2NiAzNi42MiAxODUuNTY1bDM1Ljc0Mi0yMC4zOTUgMTEuNDI4IDE5Ljc5NCAyNC4wODkgNDEuODAyWiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im04MS4zNDggMTgwLjczMS00NC43MjggNC44MzQgMzUuNzQyLTIwLjM5NSA4Ljk4NiAxNS41NjFaIiBmaWxsPSIjODFENjcyIi8+ICA8cGF0aCBkPSJNOTUuNDkzIDIwOS4yMzdjLTkuNDQ3IDIuOTY2LTE3Ljg0NSAxMC42MzctMjEuNjIgMjEuNTUyLS40OTcgMS41ODktMi42NzggMS41ODktMy4yNzIgMC0zLjI3Mi0xMC4yMy0xMS40MDUtMTguMjc2LTIxLjUyLTIxLjU1Mi0xLjc4NC0uNTk4LTEuNzg0LTIuNzgyIDAtMy4zNzcgMTAuMTE1LTMuMzEyIDE4LjE3NC0xMS41MDYgMjEuNTItMjEuNTUyLjU5NC0xLjY4OSAyLjc3OC0xLjY4OSAzLjI3MiAwIDMuNzY4IDEwLjY4OSAxMS41NjMgMTguMTk1IDIxLjYyIDIxLjU1MiAxLjY4Ny41OTUgMS42ODcgMi43NzkgMCAzLjM3N1oiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjU2Ljg5OCAzODEuNjA5LTEzNS44NDYgNzguNTI3LS44NzctMjIxLjU1MSAxMzUuODQ5LTc4LjU2Ljg3NCAyMjEuNTg0WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0yMTAuNDg2IDQwOC40NDUtNDEuMTAxIDIzLjc0NS0uODc1LTIyMS41NTEgNDEuMTAyLTIzLjc3OC44NzQgMjIxLjU4NFoiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjQwLjkwMSAzNjQuOTQ5LTEwNC40MDcgNjAuMzg3LS4zMjMtMTU3LjQ3NyAxMDQuNDA4LTYwLjM1MS4zMjIgMTU3LjQ0MVoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTk1Ljc4OSAyNjguMDI1YzIzLjEzNy02LjcxNCAzNi44NzUgMTAuNjMxIDMyLjMwNiAzNS4yMzMtNC4wMiAyMS42NTItMjEuMzUyIDQyLjg0NS0zOS43NjkgNDkuODIxLTE5LjE3MSA3LjI2LTM1LjcxNy0yLjI2OC0zNi4yOTctMjMuOTY2LS42NjUtMjQuOTIyIDE5LjQxMy01NC4wMjEgNDMuNzYtNjEuMDg4WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMDYuNDE3IDI3NS42MTUtMjguMDggNzMuNTc3cy0yNC41NjktMzUuMzk3IDI4LjA4LTczLjU3N1ptLTIzLjAyNyA2OC4zNjIgMTkuNTYxLTUwLjkxNnMyMy44MzEgMTcuMTg5LTE5LjU2MSA1MC45MTZaIiBmaWxsPSIjZmZmIi8+PHRleHQgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjAiIHg9IjIwIiB5PSI0OTAiIGZpbGw9ImJsYWNrIiA+PHRzcGFuIGR5PSIwIiB4PSIyMCI+MS40OSBCUEYgUmVtYWluaW5nIDwvdHNwYW4+PC90ZXh0Pjwvc3ZnPg==" + const availableDataImage = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjk0IiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDI5NCA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0xNjQuNDcgMzI3LjI0MSAyOC42MjUgNDA1Ljc2OGwtLjg3OC0yMjEuNTUxIDEzNS44NDktNzguNTU5Ljg3NCAyMjEuNTgzWiIgZmlsbD0iIzNEQUE0NyIvPjxwYXRoIGQ9Im0xMTguMDU5IDM1NC4wNzctNDEuMTAyIDIzLjc0Ni0uODc0LTIyMS41NTEgNDEuMTAxLTIzLjc3OC44NzUgMjIxLjU4M1oiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjYuODI1IDE4NC4yNDIuODcgMjIxLjU2NyA5My4zNjcgNTQuMzM5LS44NzEtMjIxLjU2NC05My4zNjYtNTQuMzQyWm0xMzYuNDMyLTc4LjI2Mi44NzEgMjIxLjU2OCA5My4zNjcgNTQuMzM4LS44NzEtMjIxLjU2NC05My4zNjctNTQuMzQyWiIgZmlsbD0iIzNEQjU0MiIvPjxwYXRoIGQ9Ik03Ni42MzQgMTYyLjkxNSAyMTIgODQuMTMzbDQ0LjAzNCA3NS45MDktMTM1Ljg0MiA3OC41NDQtNDMuNTU3LTc1LjY3MVoiIGZpbGw9IiM4MUQ2NzIiLz48cGF0aCBkPSJtMTI0Ljk2NiAxMzQuOTcgNDAuNjI0LTI0LjAwMSA0NC4wMzEgNzUuOTA2LTQxLjA5OCAyMy43NjUtNDMuNTU3LTc1LjY3WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMTIuMTI1IDQ3LjkxOC0uMTE2IDM2LjIyOC0xMzUuMzk0IDc4Ljc2Ni4xMTYtMzYuMTdjMC0yLjAzMi0xLjM5LTQuNDEzLTMuMTMtNS40NTctLjg3LS41MjMtMS42OC0uNTIzLTIuMjYxLS4yMzNsMTM1LjM5NC03OC43NjZjLjU4LS4zNDkgMS4zMzItLjI5IDIuMjAzLjIzMyAxLjczNi45ODkgMy4xODggMy40MjUgMy4xODggNS40WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0xNjUuNzEzIDc0Ljc1Mi0uMTE2IDM2LjIyOC00MC42NSAyMy45ODguMTE2LTM2LjE3YzAtMi4wMzItMS4zOS00LjQxMy0zLjEyOS01LjQ1Ny0uODcyLS41MjMtMS42ODEtLjUyMy0yLjI2Mi0uMjMybDQwLjY1LTIzLjk4OWMuNTgtLjM0OSAxLjMzMi0uMjkgMi4yMDMuMjMzIDEuNzM5Ljk4NiAzLjE4OCAzLjQyNSAzLjE4OCA1LjRaIiBmaWxsPSIjNDJBODRDIi8+PHBhdGggZD0iTTczLjU3OSAxMjEuMjk4YzEuNzM5IDEuMDA1IDMuMTYyIDMuNDIyIDMuMTU5IDUuNDI1bC0uMTA0IDM2LjE5MyA0My41NTcgNzUuNjY3LTkzLjM2Ni01NC4zMzkgNDMuNTIxLTI1LjAxOC4xMDMtMzYuMTQxYy4wMDQtMiAxLjM5LTIuNzk1IDMuMTMtMS43ODdaIiBmaWxsPSIjMkM5QTJDIi8+PHBhdGggZD0iTTEwNy44NzkgMjI2Ljc2NiAzNi42MiAxODUuNTY1bDM1Ljc0Mi0yMC4zOTUgMTEuNDI4IDE5Ljc5NCAyNC4wODkgNDEuODAyWiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im04MS4zNDggMTgwLjczMS00NC43MjggNC44MzQgMzUuNzQyLTIwLjM5NSA4Ljk4NiAxNS41NjFaIiBmaWxsPSIjODFENjcyIi8+ICA8cGF0aCBkPSJNOTUuNDkzIDIwOS4yMzdjLTkuNDQ3IDIuOTY2LTE3Ljg0NSAxMC42MzctMjEuNjIgMjEuNTUyLS40OTcgMS41ODktMi42NzggMS41ODktMy4yNzIgMC0zLjI3Mi0xMC4yMy0xMS40MDUtMTguMjc2LTIxLjUyLTIxLjU1Mi0xLjc4NC0uNTk4LTEuNzg0LTIuNzgyIDAtMy4zNzcgMTAuMTE1LTMuMzEyIDE4LjE3NC0xMS41MDYgMjEuNTItMjEuNTUyLjU5NC0xLjY4OSAyLjc3OC0xLjY4OSAzLjI3MiAwIDMuNzY4IDEwLjY4OSAxMS41NjMgMTguMTk1IDIxLjYyIDIxLjU1MiAxLjY4Ny41OTUgMS42ODcgMi43NzkgMCAzLjM3N1oiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjU2Ljg5OCAzODEuNjA5LTEzNS44NDYgNzguNTI3LS44NzctMjIxLjU1MSAxMzUuODQ5LTc4LjU2Ljg3NCAyMjEuNTg0WiIgZmlsbD0iIzZEQ0I2MCIvPjxwYXRoIGQ9Im0yMTAuNDg2IDQwOC40NDUtNDEuMTAxIDIzLjc0NS0uODc1LTIyMS41NTEgNDEuMTAyLTIzLjc3OC44NzQgMjIxLjU4NFoiIGZpbGw9IiMzREFBNDciLz48cGF0aCBkPSJtMjQwLjkwMSAzNjQuOTQ5LTEwNC40MDcgNjAuMzg3LS4zMjMtMTU3LjQ3NyAxMDQuNDA4LTYwLjM1MS4zMjIgMTU3LjQ0MVoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTk1Ljc4OSAyNjguMDI1YzIzLjEzNy02LjcxNCAzNi44NzUgMTAuNjMxIDMyLjMwNiAzNS4yMzMtNC4wMiAyMS42NTItMjEuMzUyIDQyLjg0NS0zOS43NjkgNDkuODIxLTE5LjE3MSA3LjI2LTM1LjcxNy0yLjI2OC0zNi4yOTctMjMuOTY2LS42NjUtMjQuOTIyIDE5LjQxMy01NC4wMjEgNDMuNzYtNjEuMDg4WiIgZmlsbD0iIzQ2Qjk1NSIvPjxwYXRoIGQ9Im0yMDYuNDE3IDI3NS42MTUtMjguMDggNzMuNTc3cy0yNC41NjktMzUuMzk3IDI4LjA4LTczLjU3N1ptLTIzLjAyNyA2OC4zNjIgMTkuNTYxLTUwLjkxNnMyMy44MzEgMTcuMTg5LTE5LjU2MSA1MC45MTZaIiBmaWxsPSIjZmZmIi8+PHRleHQgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjAiIHg9IjIwIiB5PSI0OTAiIGZpbGw9ImJsYWNrIiA+PHRzcGFuIGR5PSIwIiB4PSIyMCI+MS41MCBCUEYgUmVtYWluaW5nIDwvdHNwYW4+PC90ZXh0Pjwvc3ZnPg==" const availabletokenId = 3500001; // non minted fert id const uri = await this.fert.uri(availabletokenId); @@ -872,19 +872,18 @@ describe('Fertilize', function () { // BPF Remaining json attribute check expect(jsonResponse.attributes[0].trait_type).to.be.equal(`BPF Remaining`); - expect(jsonResponse.attributes[0].value.toString()).to.be.equal(`1.49`); + expect(jsonResponse.attributes[0].value.toString()).to.be.equal(`1.5`); }); // Active fert test it("returns an active fertilizer svg and stats when bpfRemaining > 0 and fert supply > 0", async function () { // Manipulate bpf to 5000000 - await this.fertilizer.setBpf(5000000); + await this.fertilizer.setBpf(2000000); // uint128 endBpf = totalbpf (s.bpf) + current season bpf; // So endbpf = 5000000 + 3500000 = 8500000 - // bpfRemaining = (s.bpf) - id; - // bpfRemaining for id 3500000 = 5000000 - 3500000 = 1500000 + // bpfRemaining = id - s.bpf; // so 3500000 - 2000000 = 1500000 // so bpfRemaining > 0 --> and fertsupply = 50 --> Active // s.bpf = bpfremaining + id @@ -911,11 +910,13 @@ describe('Fertilize', function () { // Used fert test it("returns a used fertilizer svg and stats when bpfRemaining = 0", async function () { + // Manipulate bpf to 3500000 + await this.fertilizer.setBpf(3500000); + // bpf is 0 // uint128 endBpf = totalbpf (s.bpf) + current season bpf; // endbpf = 0 + 3500000 = 3500000 - // bpfRemaining = (s.bpf) - id; ---> 0 - 3500000 = -3500000 --> 340282366920938... because of underflow - // bpfremaining --> now returns 0 beacause of calcualte bpfRemaining function check + // bpfRemaining = id - s.bpf ---> 3500000 - 3500000 = 0 // so bpfRemaining = 0 --> Used // This returns a used image of fert From 899fa09ceefa46fd0b780e0219eb050dfb4233f0 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 18:20:38 +0200 Subject: [PATCH 74/86] add await. --- protocol/test/Weather.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/test/Weather.test.js b/protocol/test/Weather.test.js index b55be9ff81..05420d2f88 100644 --- a/protocol/test/Weather.test.js +++ b/protocol/test/Weather.test.js @@ -82,7 +82,7 @@ describe('Complex Weather', function () { await this.fertilizer.setFertilizerE(false, to6('0')) await this.season.setYieldE(this.testData.startingWeather) await this.season.setBeanToMaxLpGpPerBdvRatio(to18(this.testData.initialPercentToLp)) - this.bean.connect(user).burn(await this.bean.balanceOf(userAddress)) + await this.bean.connect(user).burn(await this.bean.balanceOf(userAddress)) this.dsoil = this.testData.lastSoil this.startSoil = this.testData.startingSoil this.endSoil = this.testData.endingSoil From f24ccb686d6b23c93616c2b338cd198d647ea14c Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 23:24:42 +0200 Subject: [PATCH 75/86] update tests to properly match/use latest blocks. --- protocol/scripts/bips.js | 59 +++--- protocol/test/LockedBeansMainnet.test.js | 244 +++++++++++------------ 2 files changed, 147 insertions(+), 156 deletions(-) diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index 497cf652f5..9399873008 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -361,31 +361,24 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve "FertilizerFacet" ], libraryNames: [ - 'LibGauge', - 'LibIncentive', - 'LibLockedUnderlying', - 'LibWellMinting', - 'LibGerminate', - 'LibConvert' + "LibGauge", + "LibIncentive", + "LibLockedUnderlying", + "LibWellMinting", + "LibGerminate", + "LibConvert" ], facetLibraries: { - 'UnripeFacet': [ - 'LibLockedUnderlying' - ], - 'ConvertFacet': [ - 'LibConvert', - ], - 'SeasonFacet': [ - 'LibGauge', - 'LibIncentive', - 'LibLockedUnderlying', - 'LibWellMinting', - 'LibGerminate' - ], - 'SeasonGettersFacet': [ - 'LibLockedUnderlying', - 'LibWellMinting', + UnripeFacet: ["LibLockedUnderlying"], + ConvertFacet: ["LibConvert"], + SeasonFacet: [ + "LibGauge", + "LibIncentive", + "LibLockedUnderlying", + "LibWellMinting", + "LibGerminate" ], + SeasonGettersFacet: ["LibLockedUnderlying", "LibWellMinting"] }, selectorsToRemove: [], bip: false, @@ -396,14 +389,14 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve }); } -exports.bip29 = bip29 -exports.bip30 = bip30 -exports.bip34 = bip34 -exports.bipMorningAuction = bipMorningAuction -exports.bipNewSilo = bipNewSilo -exports.bipBasinIntegration = bipBasinIntegration -exports.bipSeedGauge = bipSeedGauge -exports.mockBeanstalkAdmin = mockBeanstalkAdmin -exports.bipMigrateUnripeBean3CrvToBeanEth = bipMigrateUnripeBean3CrvToBeanEth -exports.bipMigrateUnripeBeanEthToBeanSteth = bipMigrateUnripeBeanEthToBeanSteth -exports.bipMiscellaneousImprovements = bipMiscellaneousImprovements \ No newline at end of file +exports.bip29 = bip29; +exports.bip30 = bip30; +exports.bip34 = bip34; +exports.bipMorningAuction = bipMorningAuction; +exports.bipNewSilo = bipNewSilo; +exports.bipBasinIntegration = bipBasinIntegration; +exports.bipSeedGauge = bipSeedGauge; +exports.mockBeanstalkAdmin = mockBeanstalkAdmin; +exports.bipMigrateUnripeBean3CrvToBeanEth = bipMigrateUnripeBean3CrvToBeanEth; +exports.bipMigrateUnripeBeanEthToBeanSteth = bipMigrateUnripeBeanEthToBeanSteth; +exports.bipMiscellaneousImprovements = bipMiscellaneousImprovements; diff --git a/protocol/test/LockedBeansMainnet.test.js b/protocol/test/LockedBeansMainnet.test.js index 25ac5af6b8..321fbdd970 100644 --- a/protocol/test/LockedBeansMainnet.test.js +++ b/protocol/test/LockedBeansMainnet.test.js @@ -1,25 +1,24 @@ -const { BEAN, UNRIPE_BEAN, UNRIPE_LP, BEAN_ETH_WELL, BEANSTALK } = require('./utils/constants.js'); -const { EXTERNAL, INTERNAL } = require('./utils/balances.js') -const { impersonateSigner, impersonateBeanstalkOwner } = require('../utils/signer.js'); +const { BEAN, UNRIPE_BEAN, UNRIPE_LP, BEAN_ETH_WELL, BARN_RAISE_WELL, BEANSTALK, WSTETH } = require("./utils/constants.js"); +const { EXTERNAL, INTERNAL } = require("./utils/balances.js"); +const { impersonateSigner, impersonateBeanstalkOwner } = require("../utils/signer.js"); const { takeSnapshot, revertToSnapshot } = require("./utils/snapshot.js"); -const { getBeanstalk } = require('../utils/contracts.js'); +const { getBeanstalk } = require("../utils/contracts.js"); const { upgradeWithNewFacets } = require("../scripts/diamond"); -const { bipSeedGauge, bipMiscellaneousImprovements } = require('../scripts/bips.js'); -const { to6, to18 } = require('./utils/helpers.js'); -const { mintEth } = require('../utils') -const { ethers } = require('hardhat'); -const { expect } = require('chai'); +const { bipMiscellaneousImprovements } = require("../scripts/bips.js"); +const { migrateBeanEthToBeanWSteth } = require("../scripts/beanWstethMigration.js"); +const { impersonateWsteth } = require("../scripts/impersonate.js"); +const { to6, to18 } = require("./utils/helpers.js"); +const { mintEth } = require("../utils"); +const { ethers } = require("hardhat"); +const { expect } = require("chai"); -let user,user2, owner; +let user, user2, owner; -let snapshotId +let snapshotId; - -describe('LockedBeansMainnet', function () { +describe("LockedBeansMainnet", function () { before(async function () { - - [user, user2] = await ethers.getSigners() - + [user, user2] = await ethers.getSigners(); try { await network.provider.request({ @@ -28,136 +27,107 @@ describe('LockedBeansMainnet', function () { { forking: { jsonRpcUrl: process.env.FORKING_RPC, - blockNumber: 19785088 //a random semi-recent block close to Grown Stalk Per Bdv pre-deployment - }, - }, - ], + blockNumber: 20375900 + } + } + ] }); - } catch(error) { - console.log('forking error in seed Gauge'); + } catch (error) { + console.log("forking error in seed Gauge"); console.log(error); - return + return; } - this.beanstalk = await getBeanstalk() + this.beanstalk = await getBeanstalk(); }); beforeEach(async function () { - snapshotId = await takeSnapshot() + snapshotId = await takeSnapshot(); }); afterEach(async function () { - await revertToSnapshot(snapshotId) + await revertToSnapshot(snapshotId); }); /** * the following tests are performed prior to the seed gauge deployment. * upon the bips passing, the tests should be updated to the latest block and omit the seed gauge update. */ - describe("chopRate change:", async function() { + describe("chopRate change:", async function () { it("correctly updates chop", async function () { // check chop rate: - expect(await this.beanstalk.getPercentPenalty(UNRIPE_BEAN)).to.eq(to6('0.010227')) - expect(await this.beanstalk.getPercentPenalty(UNRIPE_LP)).to.eq(to6('0.010215')) - - // simulate a urBean chop: - address = await impersonateSigner('0xef764bac8a438e7e498c2e5fccf0f174c3e3f8db') - snapshotId = await takeSnapshot() - await this.beanstalk.connect(address).withdrawDeposit( - UNRIPE_BEAN, - '-28418', - to6('1'), - INTERNAL + expect(await this.beanstalk.getPercentPenalty(UNRIPE_BEAN)).to.eq(to6("0.013271")); + expect(await this.beanstalk.getPercentPenalty(UNRIPE_LP)).to.eq(to6("0.013264")); + + // simulate a urBean chop: + address = await impersonateSigner("0xef764bac8a438e7e498c2e5fccf0f174c3e3f8db"); + snapshotId = await takeSnapshot(); + await this.beanstalk + .connect(address) + .withdrawDeposit(UNRIPE_BEAN, "-28418000000", to6("1"), INTERNAL); + await this.beanstalk.connect(address).chop(UNRIPE_BEAN, to6("1"), INTERNAL, EXTERNAL); + expect(await this.beanstalk.getExternalBalance(address.address, BEAN)).to.eq(to6("0.013271")); + await revertToSnapshot(snapshotId); + + // simulate a urLP chop: + await this.beanstalk + .connect(address) + .withdrawDeposit(UNRIPE_LP, "-33292000000", to6("1"), INTERNAL); + await this.beanstalk.connect(address).chop(UNRIPE_LP, to6("1"), INTERNAL, EXTERNAL); + expect(await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL)).to.eq( + to18("0.000164043206705975") ); - await this.beanstalk.connect(address).chop( - UNRIPE_BEAN, - to6('1'), - INTERNAL, - EXTERNAL - ) - expect(await this.beanstalk.getExternalBalance(address.address, BEAN)).to.eq(to6('0.010226')) - await revertToSnapshot(snapshotId) - - // simulate a urLP chop: - await this.beanstalk.connect(address).withdrawDeposit( - UNRIPE_LP, - '-33292', - to6('1'), - INTERNAL - ); - await this.beanstalk.connect(address).chop( - UNRIPE_LP, - to6('1'), - INTERNAL, - EXTERNAL - ) - expect(await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL)).to.eq(to18('0.000126330680297571')) - await revertToSnapshot(snapshotId) + await revertToSnapshot(snapshotId); - // deploy seed gauge - await bipSeedGauge(true, undefined, false) + // migrate bean eth to bean wsteth: + this.wsteth = await ethers.getContractAt('MockWsteth', WSTETH) + const stethPerToken = await this.wsteth.stEthPerToken() + await impersonateWsteth() + await this.wsteth.setStEthPerToken(stethPerToken) + await migrateBeanEthToBeanWSteth(); - // // deploy misc. improvements bip - await bipMiscellaneousImprovements(true, undefined, false) + // deploy misc. improvements bip: + await bipMiscellaneousImprovements(true, undefined, false); // check chop rate: - expect(await this.beanstalk.getPercentPenalty(UNRIPE_BEAN)).to.eq(to6('0.050532')) - expect(await this.beanstalk.getPercentPenalty(UNRIPE_LP)).to.eq(to6('0.050473')) - - // simulate a urBean chop: - snapshotId = await takeSnapshot() - await this.beanstalk.connect(address).withdrawDeposit( - UNRIPE_BEAN, - '-28418000000', // scaled by 1e6 due to silo v3.1. - to6('1'), - INTERNAL - ); - await this.beanstalk.connect(address).chop( - UNRIPE_BEAN, - to6('1'), - INTERNAL, - EXTERNAL - ) - expect(await this.beanstalk.getExternalBalance(address.address, BEAN)).to.eq(to6('0.050532')) - await revertToSnapshot(snapshotId) + expect(await this.beanstalk.getPercentPenalty(UNRIPE_BEAN)).to.eq(to6("0.050552")); + expect(await this.beanstalk.getPercentPenalty(UNRIPE_LP)).to.eq(to6("0.050526")); + + // simulate a urBean chop: + snapshotId = await takeSnapshot(); + await this.beanstalk + .connect(address) + .withdrawDeposit(UNRIPE_BEAN, "-28418000000", to6("1"), INTERNAL); + await this.beanstalk.connect(address).chop(UNRIPE_BEAN, to6("1"), INTERNAL, EXTERNAL); + expect(await this.beanstalk.getExternalBalance(address.address, BEAN)).to.eq(to6("0.050552")); + await revertToSnapshot(snapshotId); // // simulate a urLP chop: - let initialBeanEthBal = await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL) - await this.beanstalk.connect(address).withdrawDeposit( - UNRIPE_LP, - '-33292000000', - to6('1'), - INTERNAL + let initialBeanEthBal = await this.beanstalk.getExternalBalance( + address.address, + BARN_RAISE_WELL ); - await this.beanstalk.connect(address).chop( - UNRIPE_LP, - to6('1'), - INTERNAL, - EXTERNAL - ) - let newBeanEthBal = await this.beanstalk.getExternalBalance(address.address, BEAN_ETH_WELL) + await this.beanstalk + .connect(address) + .withdrawDeposit(UNRIPE_LP, "-33292000000", to6("1"), INTERNAL); + await this.beanstalk.connect(address).chop(UNRIPE_LP, to6("1"), INTERNAL, EXTERNAL); + let newBeanEthBal = await this.beanstalk.getExternalBalance(address.address, BARN_RAISE_WELL); // beanEthBal should increase by ~4.94x the original chop rate. - expect(newBeanEthBal-initialBeanEthBal).to.eq(to18('0.000624219026576969')) - }) - }) + expect(newBeanEthBal - initialBeanEthBal).to.eq(to18("0.000576793427336659")); + }); + }); - describe("lockedBeans change", async function() { + describe("lockedBeans change", async function () { it("correctly updates locked beans", async function () { - // deploy mockUnripeFacet, as `getLockedBeans()` was updated: + // deploy mockUnripeFacet, as `getLockedBeans()` was updated: account = await impersonateBeanstalkOwner(); await mintEth(account.address); await upgradeWithNewFacets({ diamondAddress: BEANSTALK, - facetNames: [ - 'MockUnripeFacet' - ], - libraryNames: [ - 'LibLockedUnderlying' - ], + facetNames: ["MockUnripeFacet"], + libraryNames: ["LibLockedUnderlying"], facetLibraries: { - 'MockUnripeFacet': [ - 'LibLockedUnderlying' - ] + MockUnripeFacet: ["LibLockedUnderlying"] }, selectorsToRemove: [], bip: false, @@ -165,19 +135,47 @@ describe('LockedBeansMainnet', function () { verbose: false, account: account, verify: false - }) + }); // check underlying locked beans and locked LP: - this.unripe = await ethers.getContractAt('MockUnripeFacet', BEANSTALK) - expect(await this.unripe.getLegacyLockedUnderlyingBean()).to.eq(to6('22034476.333100')) - // expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within(to18('158853'),to18('158855')) - expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within(to18('208398'),to18('208400')) + this.unripe = await ethers.getContractAt("MockUnripeFacet", BEANSTALK); + expect(await this.unripe.getLegacyLockedUnderlyingBean()).to.eq(to6("22073747.489499")); + expect(await this.unripe.getLegacyLockedUnderlyingLP()).to.be.within( + to18("198522"), + to18("198600") + ); + + // migrate bean eth to bean wsteth: + this.wsteth = await ethers.getContractAt('MockWsteth', WSTETH) + const stethPerToken = await this.wsteth.stEthPerToken() + await impersonateWsteth() + await this.wsteth.setStEthPerToken(stethPerToken) + await migrateBeanEthToBeanWSteth(); + + // mine blocks + update timestamp for pumps to update: + for (let i = 0; i < 100; i++) { + await ethers.provider.send("evm_increaseTime", [12]); + await ethers.provider.send("evm_mine"); + } + + // call sunrise: + await this.beanstalk.sunrise(); + + for (let i = 0; i < 100; i++) { + await ethers.provider.send("evm_increaseTime", [12]); + await ethers.provider.send("evm_mine"); + } // deploy misc. improvements bip - await bipMiscellaneousImprovements(true, undefined, false) + await bipMiscellaneousImprovements(true, undefined, false); // check underlying locked beans and locked LP: - expect(await this.beanstalk.getLockedBeansUnderlyingUnripeBean()).to.eq(to6('14978575.114249')) - expect(await this.beanstalk.getLockedBeansUnderlyingUnripeLP()).to.be.within('7668288289687','7868288289687') - }) - }) -}) \ No newline at end of file + expect(await this.beanstalk.getLockedBeansUnderlyingUnripeBean()).to.eq( + to6("15397373.979201") + ); + expect(await this.beanstalk.getLockedBeansUnderlyingUnripeLP()).to.be.within( + "8372544877445", + "8372546877445" + ); + }); + }); +}); From b56ea97ab14da60d6a3b377b5a1697ef4dff7710 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 23:34:04 +0200 Subject: [PATCH 76/86] update complex weather to use high pod rate. --- protocol/test/Weather.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/test/Weather.test.js b/protocol/test/Weather.test.js index 05420d2f88..e3ddef33ea 100644 --- a/protocol/test/Weather.test.js +++ b/protocol/test/Weather.test.js @@ -138,12 +138,12 @@ describe('Complex Weather', function () { }) }) - // note: podrate is exremely low. + // note: podrate is extremely high. describe("Extreme Weather", async function () { before(async function () { await this.season.setLastDSoilE('100000'); await this.bean.mint(userAddress, '1000000000') - await this.field.incrementTotalPodsE('100000000000'); + await this.field.incrementTotalPodsE('100000000000000'); }) beforeEach(async function () { From 577d77be97418b0ed24596cf35c9048f919fd290 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 23:40:28 +0200 Subject: [PATCH 77/86] update LibLockedUnderlying spec. --- protocol/contracts/libraries/LibLockedUnderlying.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/libraries/LibLockedUnderlying.sol b/protocol/contracts/libraries/LibLockedUnderlying.sol index fa1feff604..6933d1fc5e 100644 --- a/protocol/contracts/libraries/LibLockedUnderlying.sol +++ b/protocol/contracts/libraries/LibLockedUnderlying.sol @@ -39,15 +39,14 @@ library LibLockedUnderlying { * @notice Return the % of Underlying Tokens that would be locked if all of the Unripe Tokens * were chopped. * @param unripeToken The address of the Unripe Token - * @param recapPercentPaid The % of Sprouts that have been Rinsed or are Rinsable. - * Should have 6 decimal precision. + * @param recapPercentPaid The % of the Unripe Token that has been recapitalized * * @dev Solves the below equation for N_{⌈U/i⌉}: - * N_{t+1} = N_t - i * R * N_t / (U - i * t) + * N_{t+1} = N_t - π * i / (U - i * t) * where: * - N_t is the number of Underlying Tokens at step t * - U is the starting number of Unripe Tokens - * - R is the % of Sprouts that are Rinsable or Rinsed + * - π is the amount recapitalized * - i is the number of Unripe Beans that are chopped at each step. i ~= 46,659 is used as this is aboutr * the average Unripe Beans held per Farmer with a non-zero balance. * From e5f3e30777adfe16e213d2438dc73cafbda7ef38 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 25 Jul 2024 16:02:01 +0200 Subject: [PATCH 78/86] update totalDeltaB() --- .../beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol | 9 +++++---- protocol/lib/forge-std | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol index 6030d6f523..ff6d2ad2a8 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol @@ -90,12 +90,13 @@ contract SeasonGettersFacet { /** * @notice Returns the total Delta B across all whitelisted minting liquidity pools. - * @dev The whitelisted pools are: - * - the Bean:3Crv Metapool - * - the Bean:ETH Well */ function totalDeltaB() external view returns (int256 deltaB) { - deltaB = LibWellMinting.check(C.BEAN_ETH_WELL); + address[] memory tokens = LibWhitelistedTokens.getWhitelistedWellLpTokens(); + if (tokens.length == 0) return 0; + for (uint256 i = 0; i < tokens.length; i++) { + deltaB = deltaB.add(LibWellMinting.check(tokens[i])); + } } /** diff --git a/protocol/lib/forge-std b/protocol/lib/forge-std index 978ac6fadb..07263d193d 160000 --- a/protocol/lib/forge-std +++ b/protocol/lib/forge-std @@ -1 +1 @@ -Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d From 1e7e301ed07089f64b3a33038f565d7f8e7c906a Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 25 Jul 2024 16:05:01 +0200 Subject: [PATCH 79/86] use getWhitelistedLpTokens over getWhitelistedWellLpTokens --- .../contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol b/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol index ff6d2ad2a8..75bfcd6a98 100644 --- a/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol +++ b/protocol/contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol @@ -92,7 +92,7 @@ contract SeasonGettersFacet { * @notice Returns the total Delta B across all whitelisted minting liquidity pools. */ function totalDeltaB() external view returns (int256 deltaB) { - address[] memory tokens = LibWhitelistedTokens.getWhitelistedWellLpTokens(); + address[] memory tokens = LibWhitelistedTokens.getWhitelistedLpTokens(); if (tokens.length == 0) return 0; for (uint256 i = 0; i < tokens.length; i++) { deltaB = deltaB.add(LibWellMinting.check(tokens[i])); From 235a0beb79aa8b95ae5a5a728286e3ecc6602a3e Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 25 Jul 2024 16:16:04 +0200 Subject: [PATCH 80/86] move LibLockedUnderlying generation script to scripts. --- protocol/hardhat.config.js | 2 +- protocol/scripts/bips.js | 2 ++ .../generate_locked_underlying.js | 0 .../libLockedUnderlying_code_generation}/package.json | 0 .../libLockedUnderlying_code_generation}/updated_numbers.csv | 0 5 files changed, 3 insertions(+), 1 deletion(-) rename protocol/{contracts/libraries/code_generation => scripts/libLockedUnderlying_code_generation}/generate_locked_underlying.js (100%) rename protocol/{contracts/libraries/code_generation => scripts/libLockedUnderlying_code_generation}/package.json (100%) rename protocol/{contracts/libraries/code_generation => scripts/libLockedUnderlying_code_generation}/updated_numbers.csv (100%) diff --git a/protocol/hardhat.config.js b/protocol/hardhat.config.js index 9e9a2ea368..c737dbd03b 100644 --- a/protocol/hardhat.config.js +++ b/protocol/hardhat.config.js @@ -328,7 +328,7 @@ module.exports = { version: "0.7.6", settings: { optimizer: { - enabled: false, + enabled: true, runs: 100 } } diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index 9399873008..4657cd65db 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -356,6 +356,7 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve facetNames: [ "UnripeFacet", "ConvertFacet", + "ConvertGettersFacet", "SeasonFacet", "SeasonGettersFacet", "FertilizerFacet" @@ -371,6 +372,7 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve facetLibraries: { UnripeFacet: ["LibLockedUnderlying"], ConvertFacet: ["LibConvert"], + ConvertGettersFacet: ["LibConvert"], SeasonFacet: [ "LibGauge", "LibIncentive", diff --git a/protocol/contracts/libraries/code_generation/generate_locked_underlying.js b/protocol/scripts/libLockedUnderlying_code_generation/generate_locked_underlying.js similarity index 100% rename from protocol/contracts/libraries/code_generation/generate_locked_underlying.js rename to protocol/scripts/libLockedUnderlying_code_generation/generate_locked_underlying.js diff --git a/protocol/contracts/libraries/code_generation/package.json b/protocol/scripts/libLockedUnderlying_code_generation/package.json similarity index 100% rename from protocol/contracts/libraries/code_generation/package.json rename to protocol/scripts/libLockedUnderlying_code_generation/package.json diff --git a/protocol/contracts/libraries/code_generation/updated_numbers.csv b/protocol/scripts/libLockedUnderlying_code_generation/updated_numbers.csv similarity index 100% rename from protocol/contracts/libraries/code_generation/updated_numbers.csv rename to protocol/scripts/libLockedUnderlying_code_generation/updated_numbers.csv From 10c50916acdd1a2ea8c3699217779cbbe549389e Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 25 Jul 2024 16:20:42 +0200 Subject: [PATCH 81/86] remove linked library --- protocol/scripts/bips.js | 1 - 1 file changed, 1 deletion(-) diff --git a/protocol/scripts/bips.js b/protocol/scripts/bips.js index 4657cd65db..688582dbe9 100644 --- a/protocol/scripts/bips.js +++ b/protocol/scripts/bips.js @@ -372,7 +372,6 @@ async function bipMiscellaneousImprovements(mock = true, account = undefined, ve facetLibraries: { UnripeFacet: ["LibLockedUnderlying"], ConvertFacet: ["LibConvert"], - ConvertGettersFacet: ["LibConvert"], SeasonFacet: [ "LibGauge", "LibIncentive", From eb94406193cb739552d1b4f8100b953b18d571ae Mon Sep 17 00:00:00 2001 From: Spacebean Date: Wed, 31 Jul 2024 12:21:47 +0200 Subject: [PATCH 82/86] feat: update chop --- .../ui/src/components/Chop/Actions/Chop.tsx | 30 +++++++++-------- .../ui/src/components/Common/TxnToast.tsx | 32 ++++++++++++++++--- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/projects/ui/src/components/Chop/Actions/Chop.tsx b/projects/ui/src/components/Chop/Actions/Chop.tsx index f9dc49a804..47f2b5bd8e 100644 --- a/projects/ui/src/components/Chop/Actions/Chop.tsx +++ b/projects/ui/src/components/Chop/Actions/Chop.tsx @@ -28,10 +28,8 @@ import FarmModeField from '~/components/Common/Form/FarmModeField'; import Token, { ERC20Token, NativeToken } from '~/classes/Token'; import { Beanstalk } from '~/generated/index'; import useToggle from '~/hooks/display/useToggle'; -import { useBeanstalkContract } from '~/hooks/ledger/useContract'; import useFarmerBalances from '~/hooks/farmer/useFarmerBalances'; import useTokenMap from '~/hooks/chain/useTokenMap'; -import { useSigner } from '~/hooks/ledger/useSigner'; import useAccount from '~/hooks/ledger/useAccount'; import usePreferredToken, { PreferredToken, @@ -59,6 +57,7 @@ import useFormMiddleware from '~/hooks/ledger/useFormMiddleware'; import useSdk from '~/hooks/sdk'; import useBDV from '~/hooks/beanstalk/useBDV'; import { BalanceFrom } from '~/components/Common/Form/BalanceFromRow'; +import { useUnripe } from '~/state/bean/unripe/updater'; type ChopFormValues = FormState & { destination: FarmToMode | undefined; @@ -283,9 +282,10 @@ const PREFERRED_TOKENS: PreferredToken[] = [ const Chop: FC<{}> = () => { /// Ledger + const sdk = useSdk(); const account = useAccount(); - const { data: signer } = useSigner(); - const beanstalk = useBeanstalkContract(signer); + const beanstalk = sdk.contracts.beanstalk; + const [refetchUnripe] = useUnripe(); /// Farmer const farmerBalances = useFarmerBalances(); @@ -313,7 +313,7 @@ const Chop: FC<{}> = () => { values: ChopFormValues, formActions: FormikHelpers ) => { - let txToast; + const txToast = new TransactionToast({}); try { middleware.before(); @@ -323,7 +323,7 @@ const Chop: FC<{}> = () => { if (!state.amount?.gt(0)) throw new Error('No Unfertilized token to Chop.'); - txToast = new TransactionToast({ + txToast.setToastMessages({ loading: `Chopping ${displayFullBN(state.amount)} ${ state.token.symbol }...`, @@ -339,20 +339,22 @@ const Chop: FC<{}> = () => { txToast.confirming(txn); const receipt = await txn.wait(); - await Promise.all([refetchFarmerBalances()]); // should we also refetch the penalty? + await Promise.all([refetchFarmerBalances(), refetchUnripe()]); txToast.success(receipt); formActions.resetForm(); } catch (err) { - if (txToast) { - txToast.error(err); - } else { - const errorToast = new TransactionToast({}); - errorToast.error(err); - } + txToast.error(err); formActions.setSubmitting(false); } }, - [account, beanstalk, refetchFarmerBalances, farmerBalances, middleware] + [ + account, + beanstalk, + farmerBalances, + middleware, + refetchFarmerBalances, + refetchUnripe, + ] ); return ( diff --git a/projects/ui/src/components/Common/TxnToast.tsx b/projects/ui/src/components/Common/TxnToast.tsx index 742f702e91..6fc1c82a81 100644 --- a/projects/ui/src/components/Common/TxnToast.tsx +++ b/projects/ui/src/components/Common/TxnToast.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { ContractReceipt, ContractTransaction } from 'ethers'; import toast from 'react-hot-toast'; -import { Box, IconButton, Link, Typography } from '@mui/material'; +import { Box, IconButton, Link } from '@mui/material'; import ClearIcon from '@mui/icons-material/Clear'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import useChainConstant from '~/hooks/chain/useChainConstant'; @@ -45,7 +45,15 @@ export function ToastAlert({ overflow: 'hidden', }} > - + {desc} {hash && ( @@ -125,6 +133,12 @@ type ToastMessages = { error?: string; }; +const defaultToastMessages: ToastMessages = { + loading: 'Confirming transaction...', + success: 'Transaction confirmed.', + error: 'Transaction failed.', +}; + /** * A lightweight wrapper around react-hot-toast * to minimize repetitive Toast code when issuing transactions. @@ -136,13 +150,23 @@ export default class TransactionToast { /** */ toastId: any; - constructor(messages: ToastMessages) { - this.messages = messages; + constructor(messages: Partial) { + this.messages = { + ...defaultToastMessages, + ...messages, + }; this.toastId = toast.loading(, { duration: Infinity, }); } + setToastMessages(messages: Partial) { + this.messages = { + ...this.messages, + ...messages, + }; + } + /** * Shows a loading message with Etherscan txn link while * a transaction is confirming From c9497e96650325f6258d53fa3afa0c7836f3a3da Mon Sep 17 00:00:00 2001 From: Spacebean Date: Wed, 31 Jul 2024 12:22:09 +0200 Subject: [PATCH 83/86] feat: update convert --- .../src/components/Silo/Actions/Convert.tsx | 51 +++++-------------- projects/ui/src/components/Silo/Whitelist.tsx | 3 +- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/projects/ui/src/components/Silo/Actions/Convert.tsx b/projects/ui/src/components/Silo/Actions/Convert.tsx index 80538f0148..176ef3164f 100644 --- a/projects/ui/src/components/Silo/Actions/Convert.tsx +++ b/projects/ui/src/components/Silo/Actions/Convert.tsx @@ -19,7 +19,6 @@ import { FormStateNew, FormTxnsFormState, SettingInput, - SettingSwitch, SmartSubmitButton, TxnSettings, } from '~/components/Common/Form'; @@ -66,7 +65,6 @@ import { AppState } from '~/state'; type ConvertFormValues = FormStateNew & { settings: { slippage: number; - allowUnripeConvert: boolean; }; maxAmountIn: BigNumber | undefined; tokenOut: Token | undefined; @@ -79,16 +77,6 @@ type ConvertQuoteHandlerParams = { // ----------------------------------------------------------------------- -const filterTokenList = ( - fromToken: Token, - allowUnripeConvert: boolean, - list: Token[] -): Token[] => { - if (allowUnripeConvert || !fromToken.isUnripe) return list; - - return list.filter((token) => token.isUnripe); -}; - const ConvertForm: FC< FormikProps & { /** List of tokens that can be converted to. */ @@ -103,7 +91,7 @@ const ConvertForm: FC< plantAndDoX: ReturnType; } > = ({ - tokenList: tokenListFull, + tokenList, siloBalances, handleQuote, plantAndDoX, @@ -119,27 +107,10 @@ const ConvertForm: FC< const getBDV = useBDV(); const [isChopping, setIsChopping] = useState(false); const [confirmText, setConfirmText] = useState(''); - const [choppingConfirmed, setChoppingConfirmed] = useState(false); + const [choppingConfirmed, setChoppingConfirmed] = useState(true); const unripeTokens = useSelector( (_state) => _state._bean.unripe ); - const [tokenList, setTokenList] = useState( - filterTokenList( - values.tokens[0].token, - values.settings.allowUnripeConvert, - tokenListFull - ) - ); - - useEffect(() => { - setTokenList( - filterTokenList( - values.tokens[0].token, - values.settings.allowUnripeConvert, - tokenListFull - ) - ); - }, [tokenListFull, values.settings.allowUnripeConvert, values.tokens]); const plantCrate = plantAndDoX?.crate?.bn; @@ -222,6 +193,10 @@ const ConvertForm: FC< } } + // + 16.84 BEANwstETH + // ~2,236.61 BDV // curr: 587 bdv + // + 12,664.01 SEED + useEffect(() => { if (confirmText.toUpperCase() === 'CHOP MY ASSETS') { setChoppingConfirmed(true); @@ -244,6 +219,7 @@ const ConvertForm: FC< } function showOutputBDV() { + if (isChopping) return bdvOut || ZERO_BN; return MaxBN(depositsBDV || ZERO_BN, bdvOut || ZERO_BN); } @@ -599,6 +575,11 @@ const ConvertPropProvider: FC<{ ]; }, [sdk, fromToken]); + console.log( + 'tokenlist: ', + tokenList.map((tk) => tk.symbol) + ); + /// Beanstalk const season = useSeason(); const [refetchPools] = useFetchPools(); @@ -636,7 +617,6 @@ const ConvertPropProvider: FC<{ // Settings settings: { slippage: 0.05, - allowUnripeConvert: false, }, // Token Inputs tokens: [ @@ -958,13 +938,6 @@ const ConvertPropProvider: FC<{ label="Slippage Tolerance" endAdornment="%" /> - {/* Only show the switch if we are on an an unripe silo's page */} - {fromToken.isUnripe && ( - - )} ) : ( Date: Wed, 31 Jul 2024 12:22:59 +0200 Subject: [PATCH 84/86] feat: update utils --- projects/ui/src/hooks/chain/useTokenMap.ts | 6 +++--- projects/ui/src/state/bean/unripe/updater.ts | 2 +- projects/ui/src/util/Ledger.ts | 19 ++++++++++++++----- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/projects/ui/src/hooks/chain/useTokenMap.ts b/projects/ui/src/hooks/chain/useTokenMap.ts index 5b121284d0..6754331d96 100644 --- a/projects/ui/src/hooks/chain/useTokenMap.ts +++ b/projects/ui/src/hooks/chain/useTokenMap.ts @@ -2,16 +2,16 @@ import { useMemo } from 'react'; import { Token } from '@beanstalk/sdk'; import TokenOld from '~/classes/Token'; import { ChainConstant, TokenMap } from '~/constants'; -import useGetChainToken from './useGetChainToken'; import { getTokenIndex } from '~/util'; +import useGetChainToken from './useGetChainToken'; export default function useTokenMap( - list: (T | ChainConstant)[] + list: (T | ChainConstant)[] | Set ) { const getChainToken = useGetChainToken(); return useMemo( () => - list.reduce>((acc, curr) => { + [...list].reduce>((acc, curr) => { // If this entry in the list is a Token and not a TokenMap, we // simply return the token. Otherwise we get the appropriate chain- // specific Token. This also dedupes tokens by address. diff --git a/projects/ui/src/state/bean/unripe/updater.ts b/projects/ui/src/state/bean/unripe/updater.ts index 13f999aad8..3d2338bad8 100644 --- a/projects/ui/src/state/bean/unripe/updater.ts +++ b/projects/ui/src/state/bean/unripe/updater.ts @@ -47,7 +47,7 @@ export const useUnripe = () => { // bean:3crv, which had 18 decimals return new BigNumber(result.toString()).div(1e18); } - return tokenResult(unripeTokens[addr])(result); // Is this correct ? + return tokenResult(unripeTokens[addr])(result); }), ]) ) diff --git a/projects/ui/src/util/Ledger.ts b/projects/ui/src/util/Ledger.ts index 37de6512f9..a6d80d3231 100644 --- a/projects/ui/src/util/Ledger.ts +++ b/projects/ui/src/util/Ledger.ts @@ -2,6 +2,7 @@ import { BigNumber as BNJS } from 'ethers'; import BigNumber from 'bignumber.js'; import type Token from '~/classes/Token'; import { ChainConstant, SupportedChainId } from '~/constants'; +import { Token as SdkToken } from '@beanstalk/sdk'; import { toTokenUnitsBN } from './Tokens'; import { ERROR_STRINGS } from '../constants/errors'; @@ -24,7 +25,14 @@ export const identityResult = (result: any) => result; export const bigNumberResult = (result: any) => new BigNumber(result instanceof BNJS ? result.toString() : result); -export const tokenResult = (_token: Token | ChainConstant) => { +export const tokenResult = ( + _token: SdkToken | Token | ChainConstant +) => { + if (_token instanceof SdkToken) { + return (result: any) => + toTokenUnitsBN(bigNumberResult(result), _token.decimals); + } + // If a mapping is provided, default to MAINNET decimals. // ASSUMPTION: the number of decimals are the same across all chains. const token = (_token as Token).decimals @@ -64,13 +72,14 @@ export const parseError = (error: any) => { case 'CALL_EXCEPTION': if (error.reason) { if (error.reason.includes('viem')) { - const _message = error.reason.substring(error.reason.indexOf('execution reverted: ')); + const _message = error.reason.substring( + error.reason.indexOf('execution reverted: ') + ); errorMessage.message = _message.replace('execution reverted: ', ''); return errorMessage; - } else { - errorMessage.message = error.reason.replace('execution reverted: ', ''); - return errorMessage; } + errorMessage.message = error.reason.replace('execution reverted: ', ''); + return errorMessage; } if (error.data && error.data.message) { From 0dee02acfb999b406c457cfa7e1fbb2e02c3925e Mon Sep 17 00:00:00 2001 From: Spacebean Date: Wed, 31 Jul 2024 12:26:00 +0200 Subject: [PATCH 85/86] feat: update convert --- projects/ui/src/components/Silo/Actions/Convert.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/projects/ui/src/components/Silo/Actions/Convert.tsx b/projects/ui/src/components/Silo/Actions/Convert.tsx index 176ef3164f..c018738dea 100644 --- a/projects/ui/src/components/Silo/Actions/Convert.tsx +++ b/projects/ui/src/components/Silo/Actions/Convert.tsx @@ -107,7 +107,7 @@ const ConvertForm: FC< const getBDV = useBDV(); const [isChopping, setIsChopping] = useState(false); const [confirmText, setConfirmText] = useState(''); - const [choppingConfirmed, setChoppingConfirmed] = useState(true); + const [choppingConfirmed, setChoppingConfirmed] = useState(false); const unripeTokens = useSelector( (_state) => _state._bean.unripe ); @@ -193,10 +193,6 @@ const ConvertForm: FC< } } - // + 16.84 BEANwstETH - // ~2,236.61 BDV // curr: 587 bdv - // + 12,664.01 SEED - useEffect(() => { if (confirmText.toUpperCase() === 'CHOP MY ASSETS') { setChoppingConfirmed(true); @@ -575,11 +571,6 @@ const ConvertPropProvider: FC<{ ]; }, [sdk, fromToken]); - console.log( - 'tokenlist: ', - tokenList.map((tk) => tk.symbol) - ); - /// Beanstalk const season = useSeason(); const [refetchPools] = useFetchPools(); From ad09f10d3ac5b304e4a8a13d07d44c08df211aed Mon Sep 17 00:00:00 2001 From: Spacebean Date: Mon, 5 Aug 2024 14:14:18 +0200 Subject: [PATCH 86/86] feat: update chop UI + chop-convert --- .../ui/src/components/Chop/ChopConditions.tsx | 26 +++---------------- .../src/components/Silo/Actions/Convert.tsx | 13 ++++++---- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/projects/ui/src/components/Chop/ChopConditions.tsx b/projects/ui/src/components/Chop/ChopConditions.tsx index d24df10730..12e857031d 100644 --- a/projects/ui/src/components/Chop/ChopConditions.tsx +++ b/projects/ui/src/components/Chop/ChopConditions.tsx @@ -9,13 +9,13 @@ import { } from '@mui/material'; import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; import { useSelector } from 'react-redux'; -import { displayBN, displayFullBN } from '../../util'; -import { BeanstalkPalette, FontSize } from '../App/muiTheme'; import { AppState } from '~/state'; import useChainConstant from '~/hooks/chain/useChainConstant'; import { UNRIPE_BEAN } from '~/constants/tokens'; import { FC } from '~/types'; +import { BeanstalkPalette, FontSize } from '../App/muiTheme'; +import { displayBN, displayFullBN } from '../../util'; const ChopConditions: FC<{}> = () => { const { fertilized, recapFundedPct, unfertilized } = useSelector< @@ -33,7 +33,7 @@ const ChopConditions: FC<{}> = () => { Chop Conditions - + = () => { )} - + = () => { - - - - - Debt Repaid to Fertilizer  - - - - - {pctDebtRepaid.times(100).toFixed(4)}% - - - diff --git a/projects/ui/src/components/Silo/Actions/Convert.tsx b/projects/ui/src/components/Silo/Actions/Convert.tsx index c018738dea..4b82de42f6 100644 --- a/projects/ui/src/components/Silo/Actions/Convert.tsx +++ b/projects/ui/src/components/Silo/Actions/Convert.tsx @@ -194,12 +194,16 @@ const ConvertForm: FC< } useEffect(() => { - if (confirmText.toUpperCase() === 'CHOP MY ASSETS') { - setChoppingConfirmed(true); + if (isChopping) { + if (confirmText.toUpperCase() === 'CHOP MY ASSETS') { + setChoppingConfirmed(true); + } else { + setChoppingConfirmed(false); + } } else { - setChoppingConfirmed(false); + setChoppingConfirmed(true); } - }, [confirmText, setChoppingConfirmed]); + }, [isChopping, confirmText, setChoppingConfirmed]); function getBDVTooltip(instantBDV: BigNumber, depositBDV: BigNumber) { return ( @@ -264,7 +268,6 @@ const ConvertForm: FC< tokenOut?.address === sdk.tokens.BEAN_WSTETH_WELL_LP.address); setIsChopping(chopping); - if (!chopping) setChoppingConfirmed(true); } })(); }, [sdk, setFieldValue, tokenIn, tokenOut]);