diff --git a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol index d236ab0085..643b73a6c9 100644 --- a/protocol/contracts/beanstalk/barn/FertilizerFacet.sol +++ b/protocol/contracts/beanstalk/barn/FertilizerFacet.sol @@ -114,7 +114,7 @@ contract FertilizerFacet is Invariable, ReentrancyGuard { /** * @dev Returns the amount of Fertilizer that can be purchased with `tokenAmountIn` Barn Raise tokens. * Can be used to help calculate `minFertilizerOut` in `mintFertilizer`. - * `tokenAmountIn` has 18 decimals, `getUsdEthPrice()` has 6 decimals and `fertilizerAmountOut` has 0 decimals. + * `tokenAmountIn` has 18 decimals, `getUsdPrice()` of eth has 6 decimals and `fertilizerAmountOut` has 0 decimals. */ function getMintFertilizerOut( uint256 tokenAmountIn diff --git a/protocol/contracts/beanstalk/init/reseed/L2/ReseedBean.sol b/protocol/contracts/beanstalk/init/reseed/L2/ReseedBean.sol index c29cf6762b..bb93f049c7 100644 --- a/protocol/contracts/beanstalk/init/reseed/L2/ReseedBean.sol +++ b/protocol/contracts/beanstalk/init/reseed/L2/ReseedBean.sol @@ -120,7 +120,12 @@ contract ReseedBean { bytes32 internal constant FERTILIZER_PROXY_SALT = 0x0000000000000000000000000000000000000000000000000000000000000000; +<<<<<<< reseed-mint-l2-bcm-beans + // BCM (TODO: Replace with actual L2 address) + address internal constant L2_BCM = address(0xa9bA2C40b263843C04d344727b954A545c81D043); +======= address beanWstethLP; +>>>>>>> dr-t-remediations /** * @notice deploys bean, unripe bean, unripe lp, and wells. @@ -132,6 +137,9 @@ contract ReseedBean { uint256 beanSupply, uint256 internalUrBeanSupply, uint256 internalUnripeLpSupply, + uint256 wethBeans, + uint256 wstEthBeans, + uint256 stableBeans, ExternalUnripeHolders[] calldata urBean, ExternalUnripeHolders[] calldata urBeanLP, address fertImplementation @@ -145,6 +153,10 @@ contract ReseedBean { BeanstalkERC20 unripeLP = deployUnripeLP(internalUnripeLpSupply, urBeanLP); // wells are deployed as ERC1967Proxies in order to allow for future upgrades. deployUpgradableWells(address(bean)); +<<<<<<< reseed-mint-l2-bcm-beans + // mint beans to the bcm according to the amounts in the l1 wells. + mintBeansToBCM(bean, wethBeans, wstEthBeans, stableBeans); +======= // set unripe to underlying tokens. setUnripeToUnderlyingTokens(address(unripeBean), address(bean), address(unripeLP)); } @@ -159,6 +171,7 @@ contract ReseedBean { s.sys.silo.unripeSettings[unripeBean].underlyingToken = bean; // set the underlying token of the unripe lp to BeanWstethLP. s.sys.silo.unripeSettings[unripeLP].underlyingToken = beanWstethLP; +>>>>>>> dr-t-remediations } function deployFertilizerProxy(address fertImplementation) internal { @@ -176,6 +189,18 @@ contract ReseedBean { console.log("Fertilizer Proxy implementation: ", fertImplementation); } + function mintBeansToBCM( + BeanstalkERC20 bean, + uint256 wethBeans, + uint256 wstEthBeans, + uint256 stableBeans + ) internal { + // total beans is the sum of the bean sided liquidity in the wells. + // Needed for bcm to add liquidity to the L2 wells after the migration. + uint256 totalBeans = wethBeans + wstEthBeans + stableBeans; + bean.mint(L2_BCM, totalBeans); + } + function deployBean(uint256 supply) internal returns (BeanstalkERC20) { BeanstalkERC20 bean = new BeanstalkERC20{salt: BEAN_SALT}( address(this), diff --git a/protocol/contracts/beanstalk/migration/L1RecieverFacet.sol b/protocol/contracts/beanstalk/migration/L1RecieverFacet.sol index 2efaee60c7..8bd7466498 100644 --- a/protocol/contracts/beanstalk/migration/L1RecieverFacet.sol +++ b/protocol/contracts/beanstalk/migration/L1RecieverFacet.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.20; -import {C} from "contracts/C.sol"; import {ReentrancyGuard} from "../ReentrancyGuard.sol"; import {Field} from "contracts/beanstalk/storage/Account.sol"; import {LibBytes} from "contracts/libraries/LibBytes.sol"; @@ -20,6 +19,9 @@ import {LibDiamond} from "contracts/libraries/LibDiamond.sol"; import {LibTransfer} from "contracts/libraries/Token/LibTransfer.sol"; import {IBean} from "contracts/interfaces/IBean.sol"; import {IFertilizer} from "contracts/interfaces/IFertilizer.sol"; +import {Order} from "contracts/beanstalk/market/MarketplaceFacet/Order.sol"; +import {Listing} from "contracts/beanstalk/market/MarketplaceFacet/Listing.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; /** * @author Brean @@ -45,9 +47,16 @@ contract L1RecieverFacet is ReentrancyGuard { 0xf93c255615938ba5f00fac3b427da6dfa313b4d75eff216bbec62dbea2e629a2; bytes32 internal constant FERTILIZER_MERKLE_ROOT = 0x02ec4c26c5d970fef9bc46f5fc160788669d465da31e9edd37aded2b1c95b6c2; + bytes32 internal constant PODLISTING_ORDER_MERKLE_ROOT = + 0x4a000e44e0820fdb1ef4194538de1404629221d77e7c920fa8c000ce5902d503; uint160 internal constant OFFSET = uint160(0x1111000000000000000000000000000000001111); + struct L1PodOrder { + Order.PodOrder podOrder; + uint256 beanAmount; + } + /** * @notice emitted when L1 Beans are migrated to L2. */ @@ -95,6 +104,8 @@ contract L1RecieverFacet is ReentrancyGuard { uint128 lastBpf ); + event L1OrdersMigrated(address indexed owner, address indexed receiver, L1PodOrder[] orders); + /** * @notice emitted when an account approves a reciever to recieve their assets. */ @@ -288,6 +299,37 @@ contract L1RecieverFacet is ReentrancyGuard { emit L1FertilizerMigrated(owner, reciever, fertIds, amounts, lastBpf); } + /** + * @notice Recreates the PodOrders for contract addresses. + * @dev Listings are not migrated from contracts (as no bean is + * locked, and that the listed plot may have been already filled), + * and will need to be recreated. + */ + function issuePodOrders( + address owner, + L1PodOrder[] memory orders, + bytes32[] calldata proof + ) external nonReentrant { + MigrationData storage account = s.sys.l2Migration.account[owner]; + address reciever = LibTractor._user(); + require( + account.reciever != address(0) && account.reciever == reciever, + "L2Migration: Invalid Reciever" + ); + require(!account.migratedPodOrders, "L2Migration: Orders have been migrated"); + + // verify order validity: + require(verifyOrderProof(owner, orders, proof), "L2Migration: Invalid Order"); + + // add migrated orders to account. + addPodOrders(reciever, orders); + + // set migrated order to true. + account.migratedPodOrders = true; + + emit L1OrdersMigrated(owner, reciever, orders); + } + //////////// MERKLE PROOF VERIFICATION //////////// /** @@ -362,6 +404,17 @@ contract L1RecieverFacet is ReentrancyGuard { return MerkleProof.verify(proof, FERTILIZER_MERKLE_ROOT, leaf); } + function verifyOrderProof( + address owner, + L1PodOrder[] memory orders, + bytes32[] calldata proof + ) public pure returns (bool) { + bytes32 leaf = keccak256( + bytes.concat(keccak256(abi.encode(owner, keccak256(abi.encode(owner, orders))))) + ); + return MerkleProof.verify(proof, PODLISTING_ORDER_MERKLE_ROOT, leaf); + } + //////////// MIGRATION HELPERS //////////// /** @@ -392,14 +445,15 @@ contract L1RecieverFacet is ReentrancyGuard { } /** - * @notice adds the migrated deposits to the account. + * @notice adds the migrated plots to the account. + * @dev active field is hardcoded here to conform with L1 field id. */ function addMigratedPlotsToAccount( address reciever, uint256[] calldata index, uint256[] calldata pods ) internal { - uint256 activeField = s.sys.activeField; + uint256 activeField = 0; Field storage field = s.accts[reciever].fields[activeField]; for (uint i; i < index.length; i++) { field.plots[index[i]] = pods[i]; @@ -410,6 +464,9 @@ contract L1RecieverFacet is ReentrancyGuard { /** * @notice adds the migrated internal balances to the account. + * Since global internal balances set in ReseedGlobal also reflect smart contract balances, + * we do not need to update global internal balances here, + * only balances for the individual account. */ function addMigratedInternalBalancesToAccount( address reciever, @@ -417,7 +474,9 @@ contract L1RecieverFacet is ReentrancyGuard { uint256[] calldata amounts ) internal { for (uint i; i < tokens.length; i++) { - LibBalance.increaseInternalBalance(reciever, IERC20(tokens[i]), amounts[i]); + IERC20 token = IERC20(tokens[i]); + s.accts[reciever].internalTokenBalance[token] += amounts[i]; + emit LibBalance.InternalBalanceChanged(reciever, token, SafeCast.toInt256(amounts[i])); } } @@ -440,6 +499,35 @@ contract L1RecieverFacet is ReentrancyGuard { } } + /** + * @notice adds the migrated pod orders to the account. + * @dev `orderer` is updated to the reciever. + */ + function addPodOrders(address reciever, L1PodOrder[] memory orders) internal { + for (uint i; i < orders.length; i++) { + // change orders[i].podOrder.orderer to the reciever. + orders[i].podOrder.orderer = reciever; + + // calculate new order id from new receiver, and set mapping. + bytes32 id = _getOrderId(orders[i].podOrder); + s.sys.podOrders[id] = orders[i].beanAmount; + + emit Order.PodOrderCreated( + orders[i].podOrder.orderer, + id, + orders[i].beanAmount, + orders[i].podOrder.fieldId, + orders[i].podOrder.pricePerPod, + orders[i].podOrder.maxPlaceInLine, + orders[i].podOrder.minFillAmount + ); + + // note: s.sys.orderLockedBeans is not updated, unlike in `_createPodOrder`, + // as the reseed has already included these beans in orderLockedBeans. + // (see {ReseedGlobal.setSilo()}) + } + } + function getReciever(address owner) external view returns (address) { return s.sys.l2Migration.account[owner].reciever; } @@ -483,4 +571,20 @@ contract L1RecieverFacet is ReentrancyGuard { l2Address = address(uint160(l1Address) + OFFSET); } } + + /* + * @notice internal orderId + */ + function _getOrderId(Order.PodOrder memory podOrder) internal pure returns (bytes32 id) { + return + keccak256( + abi.encodePacked( + podOrder.orderer, + podOrder.fieldId, + podOrder.pricePerPod, + podOrder.maxPlaceInLine, + podOrder.minFillAmount + ) + ); + } } diff --git a/protocol/contracts/beanstalk/migration/L2MigrationFacet.sol b/protocol/contracts/beanstalk/migration/L2MigrationFacet.sol index ba711afbd9..ebaecd867e 100644 --- a/protocol/contracts/beanstalk/migration/L2MigrationFacet.sol +++ b/protocol/contracts/beanstalk/migration/L2MigrationFacet.sol @@ -77,7 +77,7 @@ contract L2MigrationFacet is ReentrancyGuard { L2Beanstalk, 0, maxSubmissionCost, - msg.sender, + reciever, // excessFeeRefundAddress msg.sender, maxGas, gasPriceBid, @@ -114,7 +114,7 @@ contract L2MigrationFacet is ReentrancyGuard { L2Beanstalk, 0, maxSubmissionCost, - msg.sender, + receiver, // excessFeeRefundAddress msg.sender, maxGas, gasPriceBid, diff --git a/protocol/contracts/beanstalk/storage/System.sol b/protocol/contracts/beanstalk/storage/System.sol index 4bd18c879e..6eff47285e 100644 --- a/protocol/contracts/beanstalk/storage/System.sol +++ b/protocol/contracts/beanstalk/storage/System.sol @@ -48,7 +48,6 @@ struct System { uint256 reentrantStatus; uint256 farmingStatus; address ownerCandidate; - uint256 plenty; uint128 soil; uint128 beanSown; uint256 activeField; @@ -61,7 +60,6 @@ struct System { mapping(address => bytes) wellOracleSnapshots; mapping(address => TwaReserves) twaReserves; mapping(address => uint256) usdTokenPrice; - mapping(uint32 => uint256) sops; mapping(uint256 => Field) fields; mapping(uint256 => ConvertCapacity) convertCapacity; mapping(address => Implementation) oracleImplementation; @@ -410,6 +408,7 @@ struct MigrationData { bool migratedPlots; bool migratedFert; bool migratedInternalBalances; + bool migratedPodOrders; } /** diff --git a/protocol/contracts/libraries/LibDibbler.sol b/protocol/contracts/libraries/LibDibbler.sol index 5e360f7788..5da2d217ca 100644 --- a/protocol/contracts/libraries/LibDibbler.sol +++ b/protocol/contracts/libraries/LibDibbler.sol @@ -249,7 +249,7 @@ library LibDibbler { return TEMPERATURE_PRECISION; } else { // delta == 1 - return _scaleTemperature(279415312704); + return _scaleTemperature(76079978576); } } else { if (delta == 2) { diff --git a/protocol/contracts/libraries/LibGauge.sol b/protocol/contracts/libraries/LibGauge.sol index c478f6e050..33c8517db4 100644 --- a/protocol/contracts/libraries/LibGauge.sol +++ b/protocol/contracts/libraries/LibGauge.sol @@ -40,7 +40,7 @@ library LibGauge { // 24 * 30 * 6 // uint256 internal constant TARGET_SEASONS_TO_CATCHUP = 4320; //state - uint256 internal constant STALK_BDV_PRECISION = 1e4; + uint256 internal constant STALK_BDV_PRECISION = 1e10; /** * @notice Emitted when the AverageGrownStalkPerBdvPerSeason Updates. diff --git a/protocol/contracts/libraries/Oracle/LibEthUsdOracle.sol b/protocol/contracts/libraries/Oracle/LibEthUsdOracle.sol deleted file mode 100644 index a8ad63fec4..0000000000 --- a/protocol/contracts/libraries/Oracle/LibEthUsdOracle.sol +++ /dev/null @@ -1,77 +0,0 @@ -/** - * SPDX-License-Identifier: MIT - **/ - -pragma solidity ^0.8.20; - -import {LibChainlinkOracle} from "./LibChainlinkOracle.sol"; -import {LibRedundantMath256} from "contracts/libraries/LibRedundantMath256.sol"; -import {LibAppStorage, AppStorage} from "contracts/libraries/LibAppStorage.sol"; -import {C} from "contracts/C.sol"; -import {LibOracleHelpers} from "contracts/libraries/Oracle/LibOracleHelpers.sol"; - -/** - * @title Eth Usd Oracle Library - * @notice Contains functionalty to fetch a manipulation resistant ETH/USD or USD/ETH price. - * @dev - * The Oracle uses the ETH/USD or USD/ETH Chainlink Oracle to fetch the price. - * The oracle will fail (return 0) if the Chainlink Oracle is broken or frozen (See: {LibChainlinkOracle}). - **/ -library LibEthUsdOracle { - using LibRedundantMath256 for uint256; - - address constant ETH_USD_CHAINLINK_PRICE_AGGREGATOR = - 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; - - uint256 constant ETH_DECIMALS = 18; - - /** - * @dev Returns the instantaneous USD/ETH price - * Return value has 18 decimal precision. - * Returns 0 if the USD/ETH Chainlink Oracle is broken or frozen. - **/ - function getUsdEthPrice() internal view returns (uint256) { - return getUsdEthPrice(0); - } - - /** - * @dev Returns the USD/ETH price with the option of using a TWA lookback. - * Use `lookback = 0` for the instantaneous price. `lookback > 0` for a TWAP. - * Return value has 18 decimal precision. - * Returns 0 if the USD/ETH Chainlink Oracle is broken or frozen. - **/ - function getUsdEthPrice(uint256 lookback) internal view returns (uint256) { - return - LibChainlinkOracle.getTokenPrice( - ETH_USD_CHAINLINK_PRICE_AGGREGATOR, - LibChainlinkOracle.FOUR_HOUR_TIMEOUT, - ETH_DECIMALS, - lookback - ); - } - - /** - * @dev Returns the instantaneous ETH/USD price - * Return value has 6 decimal precision. - * Returns 0 if the ETH/USD Chainlink Oracle is broken or frozen. - **/ - function getEthUsdPrice() internal view returns (uint256) { - return getEthUsdPrice(0); - } - - /** - * @dev Returns the ETH/USD price with the option of using a TWA lookback. - * Use `lookback = 0` for the instantaneous price. `lookback > 0` for a TWAP. - * Return value has 6 decimal precision. - * Returns 0 if the ETH/USD Chainlink Oracle is broken or frozen. - **/ - function getEthUsdPrice(uint256 lookback) internal view returns (uint256) { - return - LibChainlinkOracle.getTokenPrice( - ETH_USD_CHAINLINK_PRICE_AGGREGATOR, - LibChainlinkOracle.FOUR_HOUR_TIMEOUT, - 0, - lookback - ); - } -} diff --git a/protocol/contracts/libraries/Oracle/LibUsdOracle.sol b/protocol/contracts/libraries/Oracle/LibUsdOracle.sol index 8802f9e5a1..1c51aba981 100644 --- a/protocol/contracts/libraries/Oracle/LibUsdOracle.sol +++ b/protocol/contracts/libraries/Oracle/LibUsdOracle.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.20; import {C} from "contracts/C.sol"; -import {LibEthUsdOracle} from "./LibEthUsdOracle.sol"; import {LibUniswapOracle} from "./LibUniswapOracle.sol"; import {LibChainlinkOracle} from "./LibChainlinkOracle.sol"; import {IUniswapV3PoolImmutables} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; @@ -130,23 +129,18 @@ library LibUsdOracle { // if token decimals != 0, Beanstalk is attempting to query the USD/TOKEN price, and // thus the price needs to be inverted. if (tokenDecimals != 0) { - // invert tokenPrice (to get CL_TOKEN/TOKEN). - // `tokenPrice` has 6 decimal precision (see {LibUniswapOracle.getTwap}). - tokenPrice = 1e12 / tokenPrice; - // return the USD/TOKEN price. - // 1e6 * 1e`n` / 1e`n` = 1e6 + // invert tokenPrice, taking tokenDecimals into account. + tokenPrice = (1e6 * (10 ** tokenDecimals)) / tokenPrice; return (tokenPrice * chainlinkTokenPrice) / (10 ** chainlinkTokenDecimals); - } else { - // return the TOKEN/USD price. - return (tokenPrice * chainlinkTokenPrice) / UNISWAP_DENOMINATOR; } + + return (tokenPrice * chainlinkTokenPrice) / UNISWAP_DENOMINATOR; } - // If the oracle implementation address is not set, use the current contract. - address target = oracleImpl.target; - if (target == address(0)) target = address(this); + // Non-zero addresses are enforced in verifyOracleImplementation, this is just an extra check. + if (oracleImpl.target == address(0)) return 0; - (bool success, bytes memory data) = target.staticcall( + (bool success, bytes memory data) = oracleImpl.target.staticcall( abi.encodeWithSelector(oracleImpl.selector, tokenDecimals, lookback, oracleImpl.data) ); diff --git a/protocol/contracts/libraries/Silo/LibWhitelist.sol b/protocol/contracts/libraries/Silo/LibWhitelist.sol index cd12a51a71..5d81f9d3c9 100644 --- a/protocol/contracts/libraries/Silo/LibWhitelist.sol +++ b/protocol/contracts/libraries/Silo/LibWhitelist.sol @@ -347,6 +347,9 @@ library LibWhitelist { abi.encodeWithSelector(0x0dfe1681) ); } else { + // external oracles must have a target address + require(oracleImplementation.target != address(0), "Whitelist: Invalid Target Address"); + // verify you passed in a callable oracle selector (success, returnData) = oracleImplementation.target.call( abi.encodeWithSelector( diff --git a/protocol/contracts/libraries/Well/LibWell.sol b/protocol/contracts/libraries/Well/LibWell.sol index b6b87c4544..4e81f1269d 100644 --- a/protocol/contracts/libraries/Well/LibWell.sol +++ b/protocol/contracts/libraries/Well/LibWell.sol @@ -163,7 +163,15 @@ library LibWell { // (i.e, seasonGetterFacet.getLiquidityToSupplyRatio()).We use LibUsdOracle // to get the price. This should never be reached during sunrise and thus // should not impact gas. - return LibUsdOracle.getTokenPrice(token).mul(twaReserves[j]).div(1e6); + // LibUsdOracle returns the price with 1e6 precision. + // twaReserves has the same decimal precision as the token. + // The return value is then used in LibEvaluate.calcLPToSupplyRatio that assumes 18 decimal precision, + // so we need to account for whitelisted tokens that have less than 18 decimals by dividing the + // precision by the token decimals. + // Here tokenUsd = 1 so 1e6 * 1eN * 1e12 / 1eN = 1e18. + + uint8 tokenDecimals = IERC20Decimals(token).decimals(); + return LibUsdOracle.getTokenPrice(token).mul(twaReserves[j]).mul(1e12).div(10 ** tokenDecimals); } /** @@ -272,6 +280,12 @@ library LibWell { } } + /** + * @notice Calculates the token price in terms of Bean by increasing + * the bean reserves of the given well by 1 and recaclulating the new reserves, + * while maintaining the same liquidity levels. + * This essentially simulates a swap of 1 Bean for the non bean token and quotes the price. + */ function calculateTokenBeanPriceFromReserves( address well, uint256 beanIndex, @@ -293,12 +307,9 @@ library LibWell { lpTokenSupply, wellFunction.data ); - uint256 delta; - if (nonBeanIndex == 1) { - delta = oldReserve - newReserve; - } else { - delta = newReserve - oldReserve; - } + // Measure the delta of the non bean reserve. + // Due to the invariant of the well function, old reserve > new reserve. + uint256 delta = oldReserve - newReserve; price = (10 ** (IERC20Decimals(nonBeanToken).decimals() + 6)) / delta; } diff --git a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol index 882c152cfd..71ea37558f 100644 --- a/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol +++ b/protocol/contracts/mocks/mockFacets/MockSeasonFacet.sol @@ -11,7 +11,6 @@ import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import "../MockToken.sol"; import "contracts/libraries/LibBytes.sol"; import {LibChainlinkOracle} from "contracts/libraries/Oracle/LibChainlinkOracle.sol"; -import {LibEthUsdOracle} from "contracts/libraries/Oracle/LibEthUsdOracle.sol"; import {LibUsdOracle} from "contracts/libraries/Oracle/LibUsdOracle.sol"; import {LibAppStorage} from "contracts/libraries/LibAppStorage.sol"; import {LibRedundantMathSigned256} from "contracts/libraries/LibRedundantMathSigned256.sol"; diff --git a/protocol/foundry.toml b/protocol/foundry.toml index 5b8533466f..363560bbee 100644 --- a/protocol/foundry.toml +++ b/protocol/foundry.toml @@ -45,7 +45,6 @@ ignored_warnings_from = [ gas_reports = ['*'] # Cache to `$HOME/.foundry/cache//`. no_storage_caching = false -no_match_contract = "ReseedStateTest" [profile.differential] match_test = "testDiff" diff --git a/protocol/hardhat.config.js b/protocol/hardhat.config.js index 56feee005d..0e005e29e0 100644 --- a/protocol/hardhat.config.js +++ b/protocol/hardhat.config.js @@ -123,6 +123,34 @@ task("reseedL2", async () => { }); }); +// example usage: +// npx hardhat measureGasUsed --start 244125439 --end 244125766 --network localhost +// currently reseed uses 3381686192 gas on Arbitrum +task("measureGasUsed") + .addParam("start", "The start block to measure gas used from") + .addParam("end", "The end block to measure gas used to") + .setAction(async (args, hre) => { + const provider = hre.ethers.provider; + // Convert string inputs to numbers + const startBlock = parseInt(args.start, 10); + const endBlock = parseInt(args.end, 10); + if (isNaN(startBlock) || isNaN(endBlock)) { + throw new Error("Invalid block numbers provided. Please ensure they are valid integers."); + } + + let totalGasUsed = hre.ethers.BigNumber.from(0); + + // Iterate through all blocks and sum up the gas used + for (let i = startBlock; i <= endBlock; i++) { + const block = await provider.getBlock(i); + totalGasUsed = totalGasUsed.add(block.gasUsed); + } + + console.log( + `Total gas used between blocks ${startBlock} and ${endBlock}: ${totalGasUsed.toString()}` + ); + }); + task("diamondABI", "Generates ABI file for diamond, includes all ABIs of facets", async () => { // The path (relative to the root of `protocol` directory) where all modules sit. const modulesDir = path.join("contracts", "beanstalk"); diff --git a/protocol/package.json b/protocol/package.json index f086818595..5fa3625fe3 100644 --- a/protocol/package.json +++ b/protocol/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "@beanstalk/wells": "0.4.1", - "@beanstalk/wells1.2": "npm:@beanstalk/wells@1.2.0-prerelease3", + "@beanstalk/wells1.2": "npm:@beanstalk/wells@1.2.0", "@ethereum-waffle/chai": "4.0.10", "@nomicfoundation/hardhat-network-helpers": "^1.0.7", "@openzeppelin/contracts": "5.0.2", diff --git a/protocol/reseed/data/exports/externalHolders/unripeBeanHolders.csv b/protocol/reseed/data/exports/externalHolders/unripeBeanHolders.csv new file mode 100644 index 0000000000..c101902f4a --- /dev/null +++ b/protocol/reseed/data/exports/externalHolders/unripeBeanHolders.csv @@ -0,0 +1,47 @@ +account,balance +0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5,107919824635094 +0x9f64674cab93986254c6329c4521c4f9737af864,412635026949 +0xc65f06b01e114414aac120d54a2e56d2b75b1f85,149041834672 +0x8afcd552709bac70a470ec1d137d535bfa4fadee,78365538823 +0x253ab8294bf3fb6ab6439abc9bc7081504db9bff,44573289187 +0x79645bfb4ef32902f4ee436a23e4a10a50789e54,24273580464 +0x2be2273452ce4c80c0f9e9180d3f0d6eedfa7923,17411461677 +0x0bfd9fc73c82be0558f3a651f10a8bd8c784f45e,15927533063 +0xe7baf7af12e6b18c8cdd32292c6f06840cf71442,14586805595 +0xa82240bb0291a8ef6e46a4f6b8abf4737b0b5257,10000000000 +0x044c53d8576d4d700e6327c954f88388ee03b8db,9655048867 +0xaa4f23a13f25e88ba710243dd59305f382376252,8798351793 +0x3c7cfaf3680953f8ca3c7e319ac2a4c35870e86c,6416085500 +0x23444f470c8760bef7424c457c30dc2d4378974b,5670131987 +0xb8da309775c696576d26ef7d25b68c103a9ab0d5,5240156695 +0xaf616daba40f81b75af5373294d4dbe29dd0e0f6,4334685782 +0x1860a5c708c1e982e293aa4338bdbcafb7cb90ac,4269000000 +0x8687c54f8a431134cde94ae3782cb7cba9963d12,3640000000 +0x93be7761f4c7153bc462a50f9eb5eb424c39c2cd,2368110074 +0x82fcd7cd3151b0ab9e4c00629f123d45ad17fa73,2105369400 +0xf658305d26c8df03e9ed3ff7c7287f7233de472d,1866470834 +0xcd5561b1be55c1fa4bba4749919a03219497b6ef,1309882178 +0x9821aac07d0724c69835367d596352aaf09c309c,1005037990 +0x575c9606cfccf6f93d2e5a0c37d2c7696bcab132,983699606 +0xd8d1fa915826a24d6103e8d0fa04020c4ebc0c5e,936175040 +0x8bea73aac4f7ef9daded46359a544f0bb2d1364f,882175040 +0xb9488bb4f6b57093eaa9a0cf0d722ed61e8039ac,795009391 +0xf05b641229bb2aa63b205ad8b423a390f7ef05a7,726877197 +0x9142a918df6208ae1be65e2be0ea9dad6067155e,567271172 +0x76e3d82b0c49f1c921d8c1093cd91c20ba23740d,510000000 +0x9b0f5ccc13fa9fc22ab6c4766e419bb2a881eb1b,496000000 +0x24294915f8b5831d710d40c277916edc0fa0ec39,336422082 +0xb1d47d39c3bb868e5e4be068b7057d4caad0b31c,295066819 +0x4b9184df8fa9f3fc008fcde7e7bbf7208ef5118d,100000000 +0x3d93420aa8512e2bc7d77cb9352d752881706638,95422611 +0xc42593f89d4b6647bff86fa309c72d5e93a9405c,50000000 +0xf2f65a8a44f0bb1a634a85b1a4eb4be4d69769b6,12568525 +0xc896e266368d3eb26219c5cc74a4941339218d86,957695 +0x542a94e6f4d9d15aae550f7097d089f273e38f85,138988 +0x40ca67ba095c038b711ad37bbebad8a586ae6f38,62697 +0x1397c24478cbe0a54572adec2a333f87ad75ac02,43337 +0xa6a0bed0732c49bd847b4b308daac15640f1ec6e,7802 +0xf8fdcac2c64ba5e0459f67b9610bd3eda11f04ba,5894 +0xd67acddb195e31ad9e09f2cbc8d7f7891a6d44bf,276 +0x3dd413fd4d03b1d8fd2c9ed34553f7dec3b26f5c,9 +0xd970ba10ed5e88b678cd39fa37daa765f6948733,1 diff --git a/protocol/reseed/data/exports/externalHolders/unripeLpHolders.csv b/protocol/reseed/data/exports/externalHolders/unripeLpHolders.csv new file mode 100644 index 0000000000..c09ca9f115 --- /dev/null +++ b/protocol/reseed/data/exports/externalHolders/unripeLpHolders.csv @@ -0,0 +1,24 @@ +account,balance +0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5,68338152592140 +0x1085057e6d9ad66e73d3cc788079155660264152,203120992714 +0xe6f00dde5f7622298c16a9e39b471d1c1c2de250,120581249548 +0x8fe7261b58a691e40f7a21d38d27965e2d3afd6e,58799943357 +0xaf616daba40f81b75af5373294d4dbe29dd0e0f6,49387419081 +0xf658305d26c8df03e9ed3ff7c7287f7233de472d,32130269456 +0xc6ac7c7acabc3585cfe16478fd4d22c1e8dc3c57,20951803116 +0xd8388ad2ff4c781903b6d9f17a60daf4e919f2ce,18909808200 +0x52c9a7e7d265a09db125b7369bc7487c589a7604,13915586466 +0xb33cb651648a99f2ffff076fd3f645fac24d460f,9737535811 +0xf2f65a8a44f0bb1a634a85b1a4eb4be4d69769b6,7652021349 +0x0bfd9fc73c82be0558f3a651f10a8bd8c784f45e,6863712510 +0x9821aac07d0724c69835367d596352aaf09c309c,6757145778 +0x5f683bd8e397e11858dab751eca248e5b2afc522,6104281597 +0x9142a918df6208ae1be65e2be0ea9dad6067155e,2986538692 +0x7f538566f85310c901172142e8a9a892f0eaf946,2951663131 +0x575c9606cfccf6f93d2e5a0c37d2c7696bcab132,2356468230 +0x76e3d82b0c49f1c921d8c1093cd91c20ba23740d,2233000000 +0xa86e29ad86d690f8b5a6a632cab8405d40a319fa,1755275296 +0xb9488bb4f6b57093eaa9a0cf0d722ed61e8039ac,651392713 +0xd45accf4512af9ceac4c3ab0e770f54212cdf9ac,500000000 +0x542a94e6f4d9d15aae550f7097d089f273e38f85,614013 +0x40ca67ba095c038b711ad37bbebad8a586ae6f38,605736 diff --git a/protocol/reseed/data/mocks/r3/L2_external_unripe_balances.json b/protocol/reseed/data/mocks/r3/L2_external_unripe_balances.json deleted file mode 100644 index f9d85971d2..0000000000 --- a/protocol/reseed/data/mocks/r3/L2_external_unripe_balances.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - [ - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"], - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"] - ], - [ - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"], - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"] - ] -] \ No newline at end of file diff --git a/protocol/reseed/data/mocks/r2/pod-listings-mock.json b/protocol/reseed/data/mocks/r3/pod-listings-mock.json similarity index 100% rename from protocol/reseed/data/mocks/r2/pod-listings-mock.json rename to protocol/reseed/data/mocks/r3/pod-listings-mock.json diff --git a/protocol/reseed/data/mocks/r2/pod-orders-mock.json b/protocol/reseed/data/mocks/r3/pod-orders-mock.json similarity index 100% rename from protocol/reseed/data/mocks/r2/pod-orders-mock.json rename to protocol/reseed/data/mocks/r3/pod-orders-mock.json diff --git a/protocol/reseed/data/mocks/r9-whitelist-mock.json b/protocol/reseed/data/mocks/r9-whitelist-mock.json index a2931084a6..12e63e3579 100644 --- a/protocol/reseed/data/mocks/r9-whitelist-mock.json +++ b/protocol/reseed/data/mocks/r9-whitelist-mock.json @@ -57,7 +57,7 @@ ["0x0000000000000000000000000000000000000000", "0x00000000", "0x00", "0x00"] ], [ - "0xBEA00ebA46820994d24E45dffc5c006bBE35FD89", + "0xBEA00A3F7aaF99476862533Fe7DcA4b50f6158cB", "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", [ "0xc84c7727", @@ -72,7 +72,7 @@ ["0x0000000000000000000000000000000000000000", "0xd8317c71", "0x00", "0x00"], ["0x0000000000000000000000000000000000000000", "0xa8b0bb83", "0x00", "0x00"] ], - ["0xBEA00ebA46820994d24E45dffc5c006bBE35FD89", true, true, true, true], + ["0xBEA00A3F7aaF99476862533Fe7DcA4b50f6158cB", true, true, true, true], [ "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", "0x00000000", diff --git a/protocol/reseed/data/r2/L2_external_unripe_balances.json b/protocol/reseed/data/r2/L2_external_unripe_balances.json new file mode 100644 index 0000000000..1fda71341e --- /dev/null +++ b/protocol/reseed/data/r2/L2_external_unripe_balances.json @@ -0,0 +1,186 @@ +[ + [ + "0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5", + "107919824635094" + ], + [ + "0x9f64674cab93986254c6329c4521c4f9737af864", + "412635026949" + ], + [ + "0xc65f06b01e114414aac120d54a2e56d2b75b1f85", + "149041834672" + ], + [ + "0x8afcd552709bac70a470ec1d137d535bfa4fadee", + "78365538823" + ], + [ + "0x253ab8294bf3fb6ab6439abc9bc7081504db9bff", + "44573289187" + ], + [ + "0x79645bfb4ef32902f4ee436a23e4a10a50789e54", + "24273580464" + ], + [ + "0x2be2273452ce4c80c0f9e9180d3f0d6eedfa7923", + "17411461677" + ], + [ + "0x0bfd9fc73c82be0558f3a651f10a8bd8c784f45e", + "15927533063" + ], + [ + "0xe7baf7af12e6b18c8cdd32292c6f06840cf71442", + "14586805595" + ], + [ + "0xa82240bb0291a8ef6e46a4f6b8abf4737b0b5257", + "10000000000" + ], + [ + "0x044c53d8576d4d700e6327c954f88388ee03b8db", + "9655048867" + ], + [ + "0xaa4f23a13f25e88ba710243dd59305f382376252", + "8798351793" + ], + [ + "0x3c7cfaf3680953f8ca3c7e319ac2a4c35870e86c", + "6416085500" + ], + [ + "0x23444f470c8760bef7424c457c30dc2d4378974b", + "5670131987" + ], + [ + "0xb8da309775c696576d26ef7d25b68c103a9ab0d5", + "5240156695" + ], + [ + "0xaf616daba40f81b75af5373294d4dbe29dd0e0f6", + "4334685782" + ], + [ + "0x1860a5c708c1e982e293aa4338bdbcafb7cb90ac", + "4269000000" + ], + [ + "0x8687c54f8a431134cde94ae3782cb7cba9963d12", + "3640000000" + ], + [ + "0x93be7761f4c7153bc462a50f9eb5eb424c39c2cd", + "2368110074" + ], + [ + "0x82fcd7cd3151b0ab9e4c00629f123d45ad17fa73", + "2105369400" + ], + [ + "0xf658305d26c8df03e9ed3ff7c7287f7233de472d", + "1866470834" + ], + [ + "0xcd5561b1be55c1fa4bba4749919a03219497b6ef", + "1309882178" + ], + [ + "0x9821aac07d0724c69835367d596352aaf09c309c", + "1005037990" + ], + [ + "0x575c9606cfccf6f93d2e5a0c37d2c7696bcab132", + "983699606" + ], + [ + "0xd8d1fa915826a24d6103e8d0fa04020c4ebc0c5e", + "936175040" + ], + [ + "0x8bea73aac4f7ef9daded46359a544f0bb2d1364f", + "882175040" + ], + [ + "0xb9488bb4f6b57093eaa9a0cf0d722ed61e8039ac", + "795009391" + ], + [ + "0xf05b641229bb2aa63b205ad8b423a390f7ef05a7", + "726877197" + ], + [ + "0x9142a918df6208ae1be65e2be0ea9dad6067155e", + "567271172" + ], + [ + "0x76e3d82b0c49f1c921d8c1093cd91c20ba23740d", + "510000000" + ], + [ + "0x9b0f5ccc13fa9fc22ab6c4766e419bb2a881eb1b", + "496000000" + ], + [ + "0x24294915f8b5831d710d40c277916edc0fa0ec39", + "336422082" + ], + [ + "0xb1d47d39c3bb868e5e4be068b7057d4caad0b31c", + "295066819" + ], + [ + "0x4b9184df8fa9f3fc008fcde7e7bbf7208ef5118d", + "100000000" + ], + [ + "0x3d93420aa8512e2bc7d77cb9352d752881706638", + "95422611" + ], + [ + "0xc42593f89d4b6647bff86fa309c72d5e93a9405c", + "50000000" + ], + [ + "0xf2f65a8a44f0bb1a634a85b1a4eb4be4d69769b6", + "12568525" + ], + [ + "0xc896e266368d3eb26219c5cc74a4941339218d86", + "957695" + ], + [ + "0x542a94e6f4d9d15aae550f7097d089f273e38f85", + "138988" + ], + [ + "0x40ca67ba095c038b711ad37bbebad8a586ae6f38", + "62697" + ], + [ + "0x1397c24478cbe0a54572adec2a333f87ad75ac02", + "43337" + ], + [ + "0xa6a0bed0732c49bd847b4b308daac15640f1ec6e", + "7802" + ], + [ + "0xf8fdcac2c64ba5e0459f67b9610bd3eda11f04ba", + "5894" + ], + [ + "0xd67acddb195e31ad9e09f2cbc8d7f7891a6d44bf", + "276" + ], + [ + "0x3dd413fd4d03b1d8fd2c9ed34553f7dec3b26f5c", + "9" + ], + [ + "0xd970ba10ed5e88b678cd39fa37daa765f6948733", + "1" + ] +] \ No newline at end of file diff --git a/protocol/reseed/data/r2/L2_external_unripe_lp_balances.json b/protocol/reseed/data/r2/L2_external_unripe_lp_balances.json new file mode 100644 index 0000000000..5048f20222 --- /dev/null +++ b/protocol/reseed/data/r2/L2_external_unripe_lp_balances.json @@ -0,0 +1,25 @@ +[ + ["0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5", "68338152592140"], + ["0x1085057e6d9ad66e73d3cc788079155660264152", "203120992714"], + ["0xe6f00dde5f7622298c16a9e39b471d1c1c2de250", "120581249548"], + ["0x8fe7261b58a691e40f7a21d38d27965e2d3afd6e", "58799943357"], + ["0xaf616daba40f81b75af5373294d4dbe29dd0e0f6", "49387419081"], + ["0xf658305d26c8df03e9ed3ff7c7287f7233de472d", "32130269456"], + ["0xc6ac7c7acabc3585cfe16478fd4d22c1e8dc3c57", "20951803116"], + ["0xd8388ad2ff4c781903b6d9f17a60daf4e919f2ce", "18909808200"], + ["0x52c9a7e7d265a09db125b7369bc7487c589a7604", "13915586466"], + ["0xb33cb651648a99f2ffff076fd3f645fac24d460f", "9737535811"], + ["0xf2f65a8a44f0bb1a634a85b1a4eb4be4d69769b6", "7652021349"], + ["0x0bfd9fc73c82be0558f3a651f10a8bd8c784f45e", "6863712510"], + ["0x9821aac07d0724c69835367d596352aaf09c309c", "6757145778"], + ["0x5f683bd8e397e11858dab751eca248e5b2afc522", "6104281597"], + ["0x9142a918df6208ae1be65e2be0ea9dad6067155e", "2986538692"], + ["0x7f538566f85310c901172142e8a9a892f0eaf946", "2951663131"], + ["0x575c9606cfccf6f93d2e5a0c37d2c7696bcab132", "2356468230"], + ["0x76e3d82b0c49f1c921d8c1093cd91c20ba23740d", "2233000000"], + ["0xa86e29ad86d690f8b5a6a632cab8405d40a319fa", "1755275296"], + ["0xb9488bb4f6b57093eaa9a0cf0d722ed61e8039ac", "651392713"], + ["0xd45accf4512af9ceac4c3ab0e770f54212cdf9ac", "500000000"], + ["0x542a94e6f4d9d15aae550f7097d089f273e38f85", "614013"], + ["0x40ca67ba095c038b711ad37bbebad8a586ae6f38", "605736"] +] diff --git a/protocol/reseed/data/mocks/r3/L2_initial_supply.json b/protocol/reseed/data/r2/L2_initial_supply.json similarity index 100% rename from protocol/reseed/data/mocks/r3/L2_initial_supply.json rename to protocol/reseed/data/r2/L2_initial_supply.json diff --git a/protocol/reseed/data/mocks/r3/L2_well_balances.json b/protocol/reseed/data/r2/L2_well_balances.json similarity index 100% rename from protocol/reseed/data/mocks/r3/L2_well_balances.json rename to protocol/reseed/data/r2/L2_well_balances.json diff --git a/protocol/reseed/data/r3/L2_external_unripe_balances.json b/protocol/reseed/data/r3/L2_external_unripe_balances.json deleted file mode 100644 index f9d85971d2..0000000000 --- a/protocol/reseed/data/r3/L2_external_unripe_balances.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - [ - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"], - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"] - ], - [ - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"], - ["0x000000009d3a9e5c7c620514e1f36905c4eb91e5", "1000000"] - ] -] \ No newline at end of file diff --git a/protocol/reseed/data/r3/L2_initial_supply.json b/protocol/reseed/data/r3/L2_initial_supply.json deleted file mode 100644 index 8205bcacde..0000000000 --- a/protocol/reseed/data/r3/L2_initial_supply.json +++ /dev/null @@ -1 +0,0 @@ -["10000000000", "10000000000", "10000000000"] diff --git a/protocol/reseed/data/r3/L2_well_balances.json b/protocol/reseed/data/r3/L2_well_balances.json deleted file mode 100644 index 7e9a48babc..0000000000 --- a/protocol/reseed/data/r3/L2_well_balances.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - ["1000000000", "10000000000000"], - ["1000000000", "1000000000000"], - ["1000000000", "1000000000"] -] diff --git a/protocol/reseed/data/r2/pod-listings.json b/protocol/reseed/data/r3/pod-listings.json similarity index 100% rename from protocol/reseed/data/r2/pod-listings.json rename to protocol/reseed/data/r3/pod-listings.json diff --git a/protocol/reseed/data/r2/pod-orders.json b/protocol/reseed/data/r3/pod-orders.json similarity index 100% rename from protocol/reseed/data/r2/pod-orders.json rename to protocol/reseed/data/r3/pod-orders.json diff --git a/protocol/reseed/dataConverts/convertExternalHolders.js b/protocol/reseed/dataConverts/convertExternalHolders.js new file mode 100644 index 0000000000..f4d0cbefe9 --- /dev/null +++ b/protocol/reseed/dataConverts/convertExternalHolders.js @@ -0,0 +1,21 @@ +const fs = require("fs"); + +function parseExternalHolders(inputFilePath, outputFilePath, contractAccounts) { + try { + const data = fs.readFileSync(inputFilePath, "utf8"); + const rows = data.trim().split("\n"); + const result = rows + .map((row) => row.split(",")) // Split each row by comma to get [address, balance] + .filter(([address]) => !contractAccounts.includes(address)) // Exclude contract accounts + .map(([address, balance]) => [address.trim(), balance.trim()]); // Trim any excess spaces + // Write the result to the output file as JSON + // Remove the first entry in the array as it is the header + result.shift(); + fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2)); + console.log("External holders JSON parsed successfully."); + } catch (err) { + console.error("Error:", err); + } +} + +exports.parseExternalHolders = parseExternalHolders; diff --git a/protocol/reseed/reseed10.js b/protocol/reseed/reseed10.js index ae78119002..1ee77714be 100644 --- a/protocol/reseed/reseed10.js +++ b/protocol/reseed/reseed10.js @@ -30,7 +30,8 @@ async function reseed10(account, L2Beanstalk, mock, verbose = true) { "FertilizerFacet", // BARN "UnripeFacet", "EnrootFacet", - "PauseFacet" // DIAMOND + "PauseFacet", // DIAMOND + "L1RecieverFacet" // MIGRATION ]; // A list of public libraries that need to be deployed separately. diff --git a/protocol/reseed/reseed2.js b/protocol/reseed/reseed2.js index bb9cd35d20..c721171896 100644 --- a/protocol/reseed/reseed2.js +++ b/protocol/reseed/reseed2.js @@ -14,9 +14,10 @@ const { const { deployBasinV1_2Components, deployUpgradeableWell } = require("../scripts/basinV1_2.js"); // Files -const INIT_SUPPLY = "./reseed/data/mocks/r3/L2_initial_supply.json"; -const INIT_WELL_BALANCES = "./reseed/data/mocks/r3/L2_well_balances.json"; -const EXTERNAL_UNRIPE = "./reseed/data/mocks/r3/L2_external_unripe_balances.json"; +const INIT_SUPPLY = "./reseed/data/r2/L2_initial_supply.json"; +const INIT_WELL_BALANCES = "./reseed/data/r2/L2_well_balances.json"; +const EXTERNAL_UNRIPE_BEAN = "./reseed/data/r2/L2_external_unripe_balances.json"; +const EXTERNAL_UNRIPE_BEAN_LP = "./reseed/data/r2/L2_external_unripe_lp_balances.json"; /** * reseed8 approves beanstalk to use the BCM's wsteth, eth, and a stablecoin, @@ -31,7 +32,8 @@ async function reseed2(account, L2Beanstalk, deployBasin = true, fertilizerImple [ethInBeanEthWell, wstEthInBeanWstEthWell, stableInBeanStableWell] = JSON.parse( await fs.readFileSync(INIT_WELL_BALANCES) ); - [urBean, urBeanLP] = JSON.parse(await fs.readFileSync(EXTERNAL_UNRIPE)); + externalUrBean = JSON.parse(await fs.readFileSync(EXTERNAL_UNRIPE_BEAN)); + externalUrBeanLP = JSON.parse(await fs.readFileSync(EXTERNAL_UNRIPE_BEAN_LP)); // mint: let weth, wsteth, stable, owner; @@ -68,8 +70,8 @@ async function reseed2(account, L2Beanstalk, deployBasin = true, fertilizerImple beanSupply, unripeBeanSupply, unripeLpSupply, - urBean, - urBeanLP, + externalUrBean, + externalUrBeanLP, fertilizerImplementation ], bip: false, diff --git a/protocol/reseed/reseed3.js b/protocol/reseed/reseed3.js index 6d59dd383f..f06cfc2ddc 100644 --- a/protocol/reseed/reseed3.js +++ b/protocol/reseed/reseed3.js @@ -9,8 +9,8 @@ async function reseed3(account, L2Beanstalk, mock) { let podListingsPath; let podOrdersPath; if (mock) { - podListingsPath = "./reseed/data/mocks/r2/pod-listings-mock.json"; - podOrdersPath = "./reseed/data/mocks/r2/pod-orders-mock.json"; + podListingsPath = "./reseed/data/mocks/r3/pod-listings-mock.json"; + podOrdersPath = "./reseed/data/mocks/r3/pod-orders-mock.json"; } else { podListingsPath = "./reseed/data/r2/pod-listings.json"; podOrdersPath = "./reseed/data/r2/pod-orders.json"; @@ -30,4 +30,4 @@ async function reseed3(account, L2Beanstalk, mock) { console.log("-----------------------------------"); } -exports.reseed3 = reseed3; +exports.reseed3 = reseed3; \ No newline at end of file diff --git a/protocol/reseed/reseed9.js b/protocol/reseed/reseed9.js index 35269825f4..a5ada352c9 100644 --- a/protocol/reseed/reseed9.js +++ b/protocol/reseed/reseed9.js @@ -2,13 +2,13 @@ const { upgradeWithNewFacets } = require("../scripts/diamond.js"); const { deployContract } = require("../scripts/contracts"); const fs = require("fs"); -async function reseed9(account, L2Beanstalk, mock = false) { +async function reseed9(account, L2Beanstalk, mock = true) { console.log("-----------------------------------"); console.log("reseed9: whitelist tokens.\n"); // Files let whitelistSettingsPath; - if (mock) { + if (true) { whitelistSettingsPath = "./reseed/data/mocks/r9-whitelist-mock.json"; } else { whitelistSettingsPath = "./reseed/data/r9-whitelist.json"; diff --git a/protocol/reseed/reseedAddLiquidityAndTransfer.js b/protocol/reseed/reseedAddLiquidityAndTransfer.js index f95d5eae55..45f289a34f 100644 --- a/protocol/reseed/reseedAddLiquidityAndTransfer.js +++ b/protocol/reseed/reseedAddLiquidityAndTransfer.js @@ -43,7 +43,6 @@ async function reseedAddLiquidityAndTransfer(account, L2Beanstalk, mock = true, console.log("-----------------------------------"); console.log("add liquidity to wells and transfers to l2 beanstalk.\n"); - // todo: update bean address once finalized. await impersonateToken("0xBEA0005B8599265D41256905A9B3073D397812E4", 6); const bean = await ethers.getContractAt( "MockToken", @@ -64,12 +63,12 @@ async function reseedAddLiquidityAndTransfer(account, L2Beanstalk, mock = true, await token.connect(account).approve(well.address, MAX_UINT256); await bean.connect(account).approve(well.address, MAX_UINT256); // add liquidity to well, to L2 Beanstalk: - console.log(`Adding liquidity to ${WellAddresses[i]} and performing a swap to update the well pump.`); + console.log(`Adding liquidity to ${WellAddresses[i]} and performing an update to the well pump.`); await well .connect(account) .addLiquidity([beanAmounts[i], nonBeanAmounts[i]], 0, L2Beanstalk, MAX_UINT256); - // perform a swap to update the well pumps and avoid "NotInitialized" error. - await well.connect(account).swapFrom(bean.address, token.address, to6("1"), 0, account.address, MAX_UINT256); + // perform a 0 liq addition to update the well pumps and avoid "NotInitialized" error. + await well.connect(account).addLiquidity([0, 0], 0, L2Beanstalk, MAX_UINT256); } } diff --git a/protocol/reseed/reseedL2.js b/protocol/reseed/reseedL2.js index 365b2f516f..468b46619b 100644 --- a/protocol/reseed/reseedL2.js +++ b/protocol/reseed/reseedL2.js @@ -5,6 +5,7 @@ const { parseDeposits } = require("./dataConverts/convertDeposits.js"); const { parseFertilizer } = require("./dataConverts/convertFert.js"); const { parsePodMarketplace } = require("./dataConverts/convertPodMarketplace.js"); const { parseGlobals } = require("./dataConverts/convertGlobal.js"); +const { parseExternalHolders } = require("./dataConverts/convertExternalHolders.js"); const { reseedDeployL2Beanstalk } = require("./reseedDeployL2Beanstalk.js"); const { reseed2 } = require("./reseed2.js"); const { reseed3 } = require("./reseed3.js"); @@ -32,7 +33,8 @@ async function reseedL2({ start = 0, end = 11, setState = true, - deployBasin = true + deployBasin = false, + addLiquidity = true }) { if (convertData) parseBeanstalkData(); // delete prev gas report @@ -117,7 +119,7 @@ async function reseedL2({ } } // adds liquidity to wells and transfer well LP tokens to l2 beanstalk: - await reseedAddLiquidityAndTransfer(l2owner, l2BeanstalkAddress, true); + if (addLiquidity) await reseedAddLiquidityAndTransfer(l2owner, l2BeanstalkAddress, true); console.log("Reseed successful."); } @@ -147,6 +149,8 @@ function parseBeanstalkData() { const storageFertPath = `./reseed/data/exports/storage-fertilizer${BLOCK_NUMBER}.json`; const storageSystemPath = `./reseed/data/exports/storage-system${BLOCK_NUMBER}.json`; const marketPath = "./reseed/data/exports/market-info20330000.json"; + const externalUnripeHoldersPath = "./reseed/data/exports/externalHolders/unripeBeanHolders.csv"; + const externalUnripeLpHoldersPath = "./reseed/data/exports/externalHolders/unripeLpHolders.csv"; parseGlobals(storageSystemPath, "./reseed/data/global.json"); parseAccountStatus(storageAccountsPath, "./reseed/data/r7-account-status.json", contractAccounts); parseInternalBalances( @@ -159,8 +163,18 @@ function parseBeanstalkData() { parseField(storageAccountsPath, "./reseed/data/r4-field.json", contractAccounts); parsePodMarketplace( marketPath, - "./reseed/data/r2/pod-listings.json", - "./reseed/data/r2/pod-orders.json" + "./reseed/data/r3/pod-listings.json", + "./reseed/data/r3/pod-orders.json" + ); + parseExternalHolders( + externalUnripeHoldersPath, + "./reseed/data/r2/L2_external_unripe_balances.json", + contractAccounts + ); + parseExternalHolders( + externalUnripeLpHoldersPath, + "./reseed/data/r2/L2_external_unripe_lp_balances.json", + contractAccounts ); } diff --git a/protocol/scripts/beanstalk-3/beanstalk-3-Contracts.js b/protocol/scripts/beanstalk-3/beanstalk-3-Contracts.js index f9364bdd50..5342a01666 100644 --- a/protocol/scripts/beanstalk-3/beanstalk-3-Contracts.js +++ b/protocol/scripts/beanstalk-3/beanstalk-3-Contracts.js @@ -8,6 +8,7 @@ const DEPOSITS = "./scripts/beanstalk-3/data/inputs/Deposits.json"; const PLOTS = "./scripts/beanstalk-3/data/inputs/Plots.json"; const INTERNAL_BALS = "./scripts/beanstalk-3/data/inputs/InternalBalances.json"; const FERTILIZERS = "./scripts/beanstalk-3/data/inputs/Fertilizers.json"; +const POD_ORDERS = "./scripts/beanstalk-3/data/inputs/PodOrders.json"; function getDepositMerkleRoot(verbose = false) { const accounts = JSON.parse(fs.readFileSync(DEPOSITS)); @@ -173,7 +174,47 @@ function getFertMerkleRoot(verbose = false) { ); } -getDepositMerkleRoot(false); -getPlotMerkleRoot(false); -getInternalBalMerkleRoot(false); -getFertMerkleRoot(false); +function getPodOrderMerkleRoot(verbose = false) { + const accounts = JSON.parse(fs.readFileSync(POD_ORDERS)); + let data = []; + let encodedData = ""; + let orderStruct = ["address", "tuple(tuple(address,uint256,uint24,uint256,uint256),uint256)[]"]; + for (let i = 0; i < accounts.length; i++) { + encodedData = ethers.utils.defaultAbiCoder.encode(orderStruct, accounts[i]); + // hash encoded data: + encodedData = ethers.utils.keccak256(encodedData); + data[i] = [accounts[i][0], encodedData]; + } + const tree = StandardMerkleTree.of(data, ["address", "bytes32"]); + + // (3) + console.log("PodOrder Merkle Root:", tree.root); + + // (4) + const treeData = tree.dump(); + const treeWithProofs = { + tree: treeData, + proofs: {} + }; + + for (const [i, v] of tree.entries()) { + const proof = tree.getProof(i); + treeWithProofs.proofs[v[0]] = proof; // Use the address as the key + + if (verbose) { + console.log("Value:", v); + console.log("Proof:", proof); + } + } + + fs.writeFileSync( + "./scripts/beanstalk-3/data/merkle/podOrder_tree.json", + JSON.stringify(treeWithProofs, null, 2) + ); +} + +// getDepositMerkleRoot(false); +// getPlotMerkleRoot(false); +// getInternalBalMerkleRoot(false); +// getFertMerkleRoot(false); +getPodOrderMerkleRoot(false); diff --git a/protocol/scripts/beanstalk-3/data/inputs/PodOrders.json b/protocol/scripts/beanstalk-3/data/inputs/PodOrders.json new file mode 100644 index 0000000000..f7f6396b53 --- /dev/null +++ b/protocol/scripts/beanstalk-3/data/inputs/PodOrders.json @@ -0,0 +1,47 @@ +[ + [ + "0x000000009d3a9e5C7c620514e1f36905C4Eb91e1", + [ + [ + ["0x000000009d3a9e5C7c620514e1f36905C4Eb91e1", "1", "100000", "1000000000000", "1000000"], + "1000000" + ] + ] + ], + [ + "0x000000009d3a9e5c7C620514e1F36905C4eB91E2", + [ + [ + ["0x000000009d3a9e5c7C620514e1F36905C4eB91E2", "1", "100000", "1000000000000", "1000000"], + "1000000" + ] + ] + ], + [ + "0x000000009D3a9E5c7C620514E1f36905c4eb91E3", + [ + [ + ["0x000000009D3a9E5c7C620514E1f36905c4eb91E3", "1", "100000", "1000000000000", "1000000"], + "1000000" + ] + ] + ], + [ + "0x000000009d3a9e5C7C620514E1F36905c4eb91e4", + [ + [ + ["0x000000009d3a9e5C7C620514E1F36905c4eb91e4", "1", "100000", "1000000000000", "1000000"], + "1000000" + ] + ] + ], + [ + "0x000000009D3a9E5c7C620514E1F36905C4eb91e5", + [ + [ + ["0x000000009D3a9E5c7C620514E1F36905C4eb91e5", "1", "100000", "1000000000000", "1000000"], + "1000000" + ] + ] + ] +] diff --git a/protocol/scripts/beanstalk-3/data/merkle/podOrder_tree.json b/protocol/scripts/beanstalk-3/data/merkle/podOrder_tree.json new file mode 100644 index 0000000000..da67159ac7 --- /dev/null +++ b/protocol/scripts/beanstalk-3/data/merkle/podOrder_tree.json @@ -0,0 +1,78 @@ +{ + "tree": { + "format": "standard-v1", + "leafEncoding": ["address", "bytes32"], + "tree": [ + "0x4a000e44e0820fdb1ef4194538de1404629221d77e7c920fa8c000ce5902d503", + "0xa6964a2a4ef727ac9201c69e83d4b6f1f70aa4bfd4415ebd959f171bdd1fcc30", + "0x9dc791f184484213529aa44fad0074c356eb252777a3c9b0516efaf0fd740650", + "0x7f22f1b3570ee253f90b9b5b130bb90a9bba994f9820f43820eb1257850467ea", + "0xe7d5a9eada9ddd23ca981cb62c1c0668339becddfdd69c463ae63ee3ebbdf50f", + "0xd65881c8ba79e4cd344ebcb1c500af5641ed08bb790042f18c888ced1b5b933c", + "0xcc4f9b28099f4211e2dc38f90ee8338ea1d97890b183e8b135408cd67cf7e3ca", + "0xa84b1c452b78b2a3abdc6ab83c128366592c5de1369e388ddee018c03fc36add", + "0x9887e2354e3cdb5d01aff524d71607cfdf3c4293c6f5711c806277fee5ad2063" + ], + "values": [ + { + "value": [ + "0x000000009d3a9e5C7c620514e1f36905C4Eb91e1", + "0x22a49133ec57fc5ffc4cf746ed3c681a3bb37da800b7a4729cbddc0b5cfc836f" + ], + "treeIndex": 6 + }, + { + "value": [ + "0x000000009d3a9e5c7C620514e1F36905C4eB91E2", + "0xf7ff58a83723d032bd22fb816b82dbe1499703857b6b8d6b76ec9585eec6f395" + ], + "treeIndex": 5 + }, + { + "value": [ + "0x000000009D3a9E5c7C620514E1f36905c4eb91E3", + "0x8054ec9b6a962049d270110f29b859433a304c8b75ba2e552c4021620250adaa" + ], + "treeIndex": 8 + }, + { + "value": [ + "0x000000009d3a9e5C7C620514E1F36905c4eb91e4", + "0x315d7a785df6f8efa63caae484bf8edbdfc24f797606f32b3da817a313a4aeb9" + ], + "treeIndex": 7 + }, + { + "value": [ + "0x000000009D3a9E5c7C620514E1F36905C4eb91e5", + "0x6c11c09e7e2ed81dd91f7fd89201d9b1950f7b0f45f011e60476ce58b3000173" + ], + "treeIndex": 4 + } + ] + }, + "proofs": { + "0x000000009d3a9e5C7c620514e1f36905C4Eb91e1": [ + "0xd65881c8ba79e4cd344ebcb1c500af5641ed08bb790042f18c888ced1b5b933c", + "0xa6964a2a4ef727ac9201c69e83d4b6f1f70aa4bfd4415ebd959f171bdd1fcc30" + ], + "0x000000009d3a9e5c7C620514e1F36905C4eB91E2": [ + "0xcc4f9b28099f4211e2dc38f90ee8338ea1d97890b183e8b135408cd67cf7e3ca", + "0xa6964a2a4ef727ac9201c69e83d4b6f1f70aa4bfd4415ebd959f171bdd1fcc30" + ], + "0x000000009D3a9E5c7C620514E1f36905c4eb91E3": [ + "0xa84b1c452b78b2a3abdc6ab83c128366592c5de1369e388ddee018c03fc36add", + "0xe7d5a9eada9ddd23ca981cb62c1c0668339becddfdd69c463ae63ee3ebbdf50f", + "0x9dc791f184484213529aa44fad0074c356eb252777a3c9b0516efaf0fd740650" + ], + "0x000000009d3a9e5C7C620514E1F36905c4eb91e4": [ + "0x9887e2354e3cdb5d01aff524d71607cfdf3c4293c6f5711c806277fee5ad2063", + "0xe7d5a9eada9ddd23ca981cb62c1c0668339becddfdd69c463ae63ee3ebbdf50f", + "0x9dc791f184484213529aa44fad0074c356eb252777a3c9b0516efaf0fd740650" + ], + "0x000000009D3a9E5c7C620514E1F36905C4eb91e5": [ + "0x7f22f1b3570ee253f90b9b5b130bb90a9bba994f9820f43820eb1257850467ea", + "0x9dc791f184484213529aa44fad0074c356eb252777a3c9b0516efaf0fd740650" + ] + } +} diff --git a/protocol/scripts/genDiamondCut.js b/protocol/scripts/genDiamondCut.js new file mode 100644 index 0000000000..62265b0e86 --- /dev/null +++ b/protocol/scripts/genDiamondCut.js @@ -0,0 +1,110 @@ +const ethers = require("ethers"); + +async function generateDiamondCut(existingFacets, newFacets) { + const cuts = []; + + // console.log("Generating diamond cut..."); + + // Helper function to convert selector strings to bytes4 + const selectorToBytes4 = (selector) => { + return selector.slice(0, 10); // Assuming selectors are already in '0x' format + }; + + // Process existing facets + const existingSelectors = new Map(); + existingFacets.forEach(facet => { + facet.selectors.forEach(selector => { + existingSelectors.set(selectorToBytes4(selector), facet.facetAddress); + }); + }); + + // console.log(`Found ${existingSelectors.size} existing selectors`); + // console.log("existing selectors: ", Array.from(existingSelectors.keys())); + + // Process new facets + for (const newFacet of newFacets) { + const addSelectors = []; + const replaceSelectors = []; + + newFacet.selectors.forEach(selector => { + const bytes4Selector = selectorToBytes4(selector); + if (existingSelectors.has(bytes4Selector)) { + replaceSelectors.push(bytes4Selector); + } else { + addSelectors.push(bytes4Selector); + } + existingSelectors.delete(bytes4Selector); + }); + + if (addSelectors.length > 0) { + cuts.push({ + facetAddress: newFacet.facetAddress, + action: 0, // Add + functionSelectors: addSelectors + }); + } + + if (replaceSelectors.length > 0) { + cuts.push({ + facetAddress: newFacet.facetAddress, + action: 1, // Replace + functionSelectors: replaceSelectors + }); + } + } + + // console.log(`Found ${existingSelectors.size} removed selectors`); + + // Handle removed selectors + if (existingSelectors.size > 0) { + cuts.push({ + facetAddress: '0x0000000000000000000000000000000000000000', + action: 2, // Remove + functionSelectors: Array.from(existingSelectors.keys()) + }); + } + + // console.log(`Generated ${cuts.length} cuts`); + // console.log("final cuts: ", cuts); + + return cuts; +} + +async function processDiamondCut(existingFacetsJson, newFacetsJson) { + try { + const existingFacets = JSON.parse(existingFacetsJson); + const newFacets = JSON.parse(newFacetsJson); + const diamondCut = await generateDiamondCut(existingFacets, newFacets); + + // Compact encoding + let encoded = ethers.utils.hexlify(ethers.utils.pack(['uint256'], [diamondCut.length])); + + for (const cut of diamondCut) { + encoded += ethers.utils.hexlify(cut.facetAddress).slice(2); + encoded += ethers.utils.hexZeroPad(ethers.utils.hexlify(cut.action), 1).slice(2); + encoded += ethers.utils.hexZeroPad(ethers.utils.hexlify(cut.functionSelectors.length), 2).slice(2); + for (const selector of cut.functionSelectors) { + encoded += selector.slice(2); + } + } + + process.stdout.write(encoded); + } catch (error) { + console.error(error); + process.exit(1); + } +} + +// Get command line arguments +const args = process.argv.slice(2); +if (args.length !== 2) { + console.error("Usage: node genDiamondCut.js "); + process.exit(1); +} + +processDiamondCut(args[0], args[1]) + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/protocol/scripts/genSelectors.js b/protocol/scripts/genSelectors.js index 2caf11d992..1c492817e9 100644 --- a/protocol/scripts/genSelectors.js +++ b/protocol/scripts/genSelectors.js @@ -1,31 +1,91 @@ const ethers = require("ethers"); const path = require("path/posix"); +const fs = require("fs"); const args = process.argv.slice(2); -async function printSelectors(contractName, artifactFolderPath = "../out") { - const contractFilePath = path.join(artifactFolderPath,`${contractName}.sol`,`${contractName}.json`); - const contractArtifact = require(contractFilePath); +function log(message) { + // turn logging on or off by commenting out this following line + // fs.appendFileSync('genSelectors.log', message + '\n'); +} + +async function printSelectors(contractName, artifactFolderPath = "out") { + try { + const contractFilePath = path.join( + process.cwd(), + artifactFolderPath, + `${contractName}.sol`, + `${contractName}.json` + ); + log(`Looking for contract at: ${contractFilePath}`); + + if (!fs.existsSync(contractFilePath)) { + log(`Contract file not found: ${contractFilePath}`); + return []; + } + + const contractArtifact = JSON.parse(fs.readFileSync(contractFilePath, "utf8")); + log(`Contract artifact loaded for ${contractName}`); + + if (!contractArtifact.methodIdentifiers) { + log(`No method identifiers found for ${contractName}`); + return []; + } + + const selectors = Object.values(contractArtifact.methodIdentifiers); + log(`Found ${selectors.length} selectors for ${contractName}`); - // Use map to prepend '0x' to each element, return selectors. - return Object.values(contractArtifact.methodIdentifiers).map(element => '0x' + element); + return selectors; + } catch (error) { + log(`Error in printSelectors for ${contractName}: ${error.message}`); + return []; + } } -async function processContracts(contractNames, defaultArtifactFolderPath = "../out") { - const promises = contractNames.map(contractName => - printSelectors(contractName, defaultArtifactFolderPath) - ); +async function processContracts(contractNames, defaultArtifactFolderPath = "./out/") { + try { + log(`Current working directory: ${process.cwd()}`); + + log(`Processing contracts: ${contractNames.join(", ")}`); + + const promises = contractNames.map((contractName) => + printSelectors(contractName, defaultArtifactFolderPath) + ); + + const results = await Promise.all(promises); + log(`All selectors retrieved. Number of contracts processed: ${results.length}`); + + // Compact encoding + let encoded = ethers.utils.hexZeroPad(ethers.BigNumber.from(results.length).toHexString(), 32); + log(`Encoded number of contracts: ${encoded}`); + + for (const selectors of results) { + encoded += ethers.utils + .hexZeroPad(ethers.BigNumber.from(selectors.length).toHexString(), 2) + .slice(2); + log(`Encoded number of selectors for a contract: ${encoded.slice(-4)}`); + for (const selector of selectors) { + encoded += selector; + log(`Encoded selector: ${selector}`); + } + } - // Wait for all printSelectors calls to complete - const results = await Promise.all(promises); - const coded = ethers.utils.defaultAbiCoder.encode(["bytes4[][]"], [results]); - process.stdout.write(coded) + log(`Final encoded data: ${encoded}`); + return encoded; + } catch (error) { + log(`Error in processContracts: ${error.message}`); + return "0x"; + } } -// We recommend this pattern to be able to use async/await everywhere processContracts(args) -.then(() => process.exit(0)) -.catch((error) => { - console.error(error); - process.exit(1); -}); \ No newline at end of file + .then((encoded) => { + log(`Writing to stdout: ${encoded}`); + process.stdout.write(encoded); + process.exit(0); + }) + .catch((error) => { + log(`Fatal error: ${error.message}`); + console.error(error); + process.exit(1); + }); diff --git a/protocol/test/foundry/Migration/L1Reciever.t.sol b/protocol/test/foundry/Migration/L1Reciever.t.sol index 1f7194b2b2..9b5907ce6e 100644 --- a/protocol/test/foundry/Migration/L1Reciever.t.sol +++ b/protocol/test/foundry/Migration/L1Reciever.t.sol @@ -4,6 +4,7 @@ pragma abicoder v2; import {TestHelper, LibTransfer, C, IMockFBeanstalk} from "test/foundry/utils/TestHelper.sol"; import {L1RecieverFacet} from "contracts/beanstalk/migration/L1RecieverFacet.sol"; +import {Order} from "contracts/beanstalk/market/MarketplaceFacet/Order.sol"; import {LibBytes} from "contracts/Libraries/LibBytes.sol"; /** @@ -14,7 +15,7 @@ interface IERC1555 { function balanceOf(address account, uint256 id) external view returns (uint256); } -contract L1RecieverFacetTest is TestHelper { +contract L1RecieverFacetTest is Order, TestHelper { // contracts for testing: address constant OWNER = address(0x000000009d3a9e5C7c620514e1f36905C4Eb91e1); address constant RECIEVER = address(0x000000009D3a9E5c7C620514E1F36905C4eb91e5); @@ -117,6 +118,35 @@ contract L1RecieverFacetTest is TestHelper { L1RecieverFacet(BEANSTALK).issueFertilizer(owner, ids, amounts, lastBpf, proof); } + function test_L2MigratePodOrder() public { + bs.setRecieverForL1Migration(address(0x000000009d3a9e5C7C620514E1F36905c4eb91e4), RECIEVER); + + ( + address owner, + L1RecieverFacet.L1PodOrder[] memory podOrders, + bytes32[] memory proof + ) = getMockPodOrder(); + + vm.prank(RECIEVER); + L1RecieverFacet(BEANSTALK).issuePodOrders( + address(0x000000009d3a9e5C7C620514E1F36905c4eb91e4), + podOrders, + proof + ); + + // update pod order with reciever to verify id: + podOrders[0].podOrder.orderer = RECIEVER; + + bytes32 id = _getOrderId(podOrders[0].podOrder); + + assertEq(bs.getPodOrder(id), podOrders[0].beanAmount); + + // verify user cannot migrate afterwords. + vm.expectRevert("L2Migration: Orders have been migrated"); + vm.prank(RECIEVER); + L1RecieverFacet(BEANSTALK).issuePodOrders(owner, podOrders, proof); + } + /** * @notice verifies only the owner or bridge can call the migration functions. */ @@ -174,7 +204,7 @@ contract L1RecieverFacetTest is TestHelper { L1RecieverFacet(BEANSTALK).issueInternalBalances(owner, tokens, amounts, proof); } - function test_L2MigrateInvalidInternalFert() public { + function test_L2MigrateInvalidFert() public { bs.setRecieverForL1Migration(OWNER, RECIEVER); ( @@ -193,6 +223,24 @@ contract L1RecieverFacetTest is TestHelper { L1RecieverFacet(BEANSTALK).issueFertilizer(owner, ids, amounts, lastBpf, proof); } + function test_L2MigrateInvalidPodOrder() public { + bs.setRecieverForL1Migration(OWNER, RECIEVER); + + ( + address owner, + L1RecieverFacet.L1PodOrder[] memory podOrders, + bytes32[] memory proof + ) = getMockPodOrder(); + + // update pod orderer + podOrders[0].podOrder.orderer = RECIEVER; + + // verify user cannot migrate afterwords. + vm.expectRevert("L2Migration: Invalid Order"); + vm.prank(RECIEVER); + L1RecieverFacet(BEANSTALK).issuePodOrders(owner, podOrders, proof); + } + // test helpers function getMockDepositData() internal @@ -278,6 +326,26 @@ contract L1RecieverFacetTest is TestHelper { return (account, ids, amounts, lastBpf, proof); } + function getMockPodOrder() + internal + returns (address, L1RecieverFacet.L1PodOrder[] memory, bytes32[] memory) + { + address account = address(0x000000009d3a9e5C7C620514E1F36905c4eb91e4); + + L1RecieverFacet.L1PodOrder[] memory podOrders = new L1RecieverFacet.L1PodOrder[](1); + podOrders[0] = L1RecieverFacet.L1PodOrder( + Order.PodOrder(account, 1, 100000, 1000000000000, 1000000), + 1000000 + ); + + bytes32[] memory proof = new bytes32[](3); + proof[0] = bytes32(0x9887e2354e3cdb5d01aff524d71607cfdf3c4293c6f5711c806277fee5ad2063); + proof[1] = bytes32(0xe7d5a9eada9ddd23ca981cb62c1c0668339becddfdd69c463ae63ee3ebbdf50f); + proof[2] = bytes32(0x9dc791f184484213529aa44fad0074c356eb252777a3c9b0516efaf0fd740650); + + return (account, podOrders, proof); + } + /** * @notice Utility function that converts the address in the L1 that submitted a tx to * the inbox to the msg.sender viewed in the L2 diff --git a/protocol/test/foundry/Migration/ReseedState.t.sol b/protocol/test/foundry/Migration/ReseedState.t.sol index 0ca8b8af56..e22cefe188 100644 --- a/protocol/test/foundry/Migration/ReseedState.t.sol +++ b/protocol/test/foundry/Migration/ReseedState.t.sol @@ -12,6 +12,12 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IWell} from "contracts/interfaces/basin/IWell.sol"; import {IFertilizer} from "contracts/interfaces/IFertilizer.sol"; import "forge-std/StdUtils.sol"; +import {BeanstalkPrice, WellPrice} from "contracts/ecosystem/price/BeanstalkPrice.sol"; +import {P} from "contracts/ecosystem/price/P.sol"; + +interface IBeanstalkPrice { + function price() external view returns (P.Prices memory p); +} /** * @notice Verfifies state and functionality of the new L2 Beanstalk @@ -26,6 +32,7 @@ contract ReseedStateTest is TestHelper { // contracts for testing: address constant L2_BEANSTALK = address(0xD1A0060ba708BC4BCD3DA6C37EFa8deDF015FB70); address constant FERTILIZER = address(0xC59f881074Bf039352C227E21980317e6b969c8A); + address constant BEANSTALK_PRICE = address(0xEfE94bE746681ed73DfD15F932f9a8e8ffDdEE56); uint256 constant FIELD_ID = 0; @@ -72,9 +79,17 @@ contract ReseedStateTest is TestHelper { accountNumber = parseAccounts(numAccounts); console.log("Number of accounts: ", accountNumber); l2Beanstalk = IMockFBeanstalk(L2_BEANSTALK); + // skip(100_000_000); // l2Beanstalk.gm(address(this), 1); } + // LibUsdOracle: 0x5003dF9E48dA96e4B4390373c8ae70EbFA5415A7 + function test_beanstalkPrice() public { + // Get beanstalk price + IBeanstalkPrice beanstalkPrice = IBeanstalkPrice(BEANSTALK_PRICE); + P.Prices memory prices = beanstalkPrice.price(); + } + ////////////////// WhiteListed Tokens ////////////////// function test_whiteListedTokens() public { diff --git a/protocol/test/foundry/silo/Oracle.t.sol b/protocol/test/foundry/silo/Oracle.t.sol index e6250e3501..d8654998f4 100644 --- a/protocol/test/foundry/silo/Oracle.t.sol +++ b/protocol/test/foundry/silo/Oracle.t.sol @@ -8,6 +8,7 @@ import {MockChainlinkAggregator} from "contracts/mocks/chainlink/MockChainlinkAg import {MockToken} from "contracts/mocks/MockToken.sol"; import {LSDChainlinkOracle} from "contracts/ecosystem/oracles/LSDChainlinkOracle.sol"; import {LibChainlinkOracle} from "contracts/libraries/Oracle/LibChainlinkOracle.sol"; +import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; /** * @notice Tests the functionality of the Oracles. @@ -63,7 +64,7 @@ contract OracleTest is TestHelper { WBTC, 0 ); - assertEq(usdTokenPriceFromExternal, 0.00002e6, "usdTokenPriceFromExternal"); + assertEq(usdTokenPriceFromExternal, 0.00002e8, "usdTokenPriceFromExternal"); // e8 because wbtc has 8 decimals } /** @@ -208,6 +209,21 @@ contract OracleTest is TestHelper { assertEq(oracleImplementation.target, WBTC_USD_CHAINLINK_PRICE_AGGREGATOR); } + function testZeroAddressOracleImplementationTarget() public { + vm.prank(BEANSTALK); + // exersizes address 0 and bytes 0x00, although there's no current way to whitelist something with these values. + vm.expectRevert("Whitelist: Invalid Target Address"); + bs.updateOracleImplementationForToken( + WBTC, + IMockFBeanstalk.Implementation( + address(0), + IMockFBeanstalk.getUsdTokenPriceFromExternal.selector, + bytes1(0x00), + abi.encode(LibChainlinkOracle.FOUR_HOUR_TIMEOUT) + ) + ); + } + function testGetTokenPrice() public { // change encode type to 0x02 for wbtc: vm.prank(BEANSTALK); @@ -260,6 +276,38 @@ contract OracleTest is TestHelper { assertEq(priceWBTC, 0.00002e8); // adjusted to 8 decimals } + function testForkMainnetWBTCOracle() public { + forkMainnetAndUpgradeAllFacets(20641000); + + setupUniswapWBTCOracleImplementation(); + + uint256 priceWBTC = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); + assertEq(priceWBTC, 1684); // $1 buys 1683 satoshi at BTC price of 6148186669379 per USDC and USDC 99993272, but 16 is 6 decimal precision + } + + function testForkMainnetAAVEOracle() public { + forkMainnetAndUpgradeAllFacets(20666000); + + setupUniswapAaveOracleImplementation(); + + uint256 priceAAVE = OracleFacet(BEANSTALK).getUsdTokenPrice(AAVE); + assertEq(priceAAVE, 7478751606516229); + // chainlink price: 2541090000 (2541 usd per weth at 6 decimals) + // uniswap price: 52620 (0.052620 WETH per AAVE at 6 decimals) + // these multiplied together: 133712155800000 (12 decimal precision) + // but inverse is needed, so 1e12/133712155800000 = 0.007478751607 + // and 0.007478751607 at 6 decimal precision is 7479 + } + + function testForkMainnetWSTETHOracle() public { + forkMainnetAndUpgradeAllFacets(20666000); + + setupUniswapWstethOracleImplementation(); + + uint256 priceWSTETH = OracleFacet(BEANSTALK).getUsdTokenPrice(WSTETH); + assertEq(priceWSTETH, 334243752683826); + } + function setupUniswapWBTCOracleImplementation() public { vm.prank(BEANSTALK); bs.updateOracleImplementationForToken( @@ -268,7 +316,7 @@ contract OracleTest is TestHelper { WBTC_USDC_03_POOL, bytes4(0), bytes1(0x02), - abi.encode(LibChainlinkOracle.FOUR_HOUR_TIMEOUT) + abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) ) ); @@ -284,4 +332,57 @@ contract OracleTest is TestHelper { ) ); } + + // AAVE:WETH is the highest volume non-memcoin/non-stablecoin pair on uniswap as of the time of writing + function setupUniswapAaveOracleImplementation() internal { + vm.prank(BEANSTALK); + bs.updateOracleImplementationForToken( + AAVE, + IMockFBeanstalk.Implementation( + AAVE_ETH_03_POOL, + bytes4(0), + bytes1(0x02), + abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) + ) + ); + + // also uniswap relies on having a chainlink oracle for the token that's trading against the uniswap target token + // in the case of AAVE/ETH, eth is the token that needs to be looked up against chainlink + vm.prank(BEANSTALK); + bs.updateOracleImplementationForToken( + WETH, + IMockFBeanstalk.Implementation( + ETH_USD_CHAINLINK_PRICE_AGGREGATOR, // note this is using eth instead of weth + bytes4(0), + bytes1(0x01), + abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) + ) + ); + } + + function setupUniswapWstethOracleImplementation() internal { + vm.prank(BEANSTALK); + bs.updateOracleImplementationForToken( + WSTETH, + IMockFBeanstalk.Implementation( + WSTETH_ETH_001_POOL, + bytes4(0), + bytes1(0x02), + abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) + ) + ); + + // also uniswap relies on having a chainlink oracle for the token that's trading against the uniswap target token + // in the case of AAVE/ETH, eth is the token that needs to be looked up against chainlink + vm.prank(BEANSTALK); + bs.updateOracleImplementationForToken( + WETH, + IMockFBeanstalk.Implementation( + ETH_USD_CHAINLINK_PRICE_AGGREGATOR, // note this is using eth instead of weth + bytes4(0), + bytes1(0x01), + abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) + ) + ); + } } diff --git a/protocol/test/foundry/silo/Whitelist.t.sol b/protocol/test/foundry/silo/Whitelist.t.sol index 89aaf177c1..4cb4e5642a 100644 --- a/protocol/test/foundry/silo/Whitelist.t.sol +++ b/protocol/test/foundry/silo/Whitelist.t.sol @@ -148,6 +148,27 @@ contract WhitelistTest is TestHelper { bytes4 oSelector = bytes4(keccak256(abi.encode(i))); vm.expectRevert("Whitelist: Invalid Oracle Implementation"); + bs.whitelistToken( + token, + IMockFBeanstalk.wellBdv.selector, + 0, + 0, + bytes1(0x01), + 0, + 0, + IMockFBeanstalk.Implementation(BEANSTALK, oSelector, bytes1(0), new bytes(0)), + IMockFBeanstalk.Implementation(BEANSTALK, gpSelector, bytes1(0), new bytes(0)), + IMockFBeanstalk.Implementation(BEANSTALK, lwSelector, bytes1(0), new bytes(0)) + ); + } + + function test_whitelistRevertZeroTargetOracleImplementation(uint i) public prank(BEANSTALK) { + address token = address(new MockWellToken()); + bytes4 gpSelector = IMockFBeanstalk.defaultGaugePointFunction.selector; + bytes4 lwSelector = IMockFBeanstalk.maxWeight.selector; + bytes4 oSelector = bytes4(keccak256(abi.encode(i))); + + vm.expectRevert("Whitelist: Invalid Target Address"); bs.whitelistToken( token, IMockFBeanstalk.wellBdv.selector, @@ -175,9 +196,9 @@ contract WhitelistTest is TestHelper { bytes1(0), 0, 0, - IMockFBeanstalk.Implementation(address(0), bytes4(0), bytes1(0x01), new bytes(0)), - IMockFBeanstalk.Implementation(address(0), gpSelector, bytes1(0), new bytes(0)), - IMockFBeanstalk.Implementation(address(0), lwSelector, bytes1(0), new bytes(0)) + IMockFBeanstalk.Implementation(BEANSTALK, bytes4(0), bytes1(0x01), new bytes(0)), + IMockFBeanstalk.Implementation(BEANSTALK, gpSelector, bytes1(0x01), new bytes(0)), + IMockFBeanstalk.Implementation(BEANSTALK, lwSelector, bytes1(0x01), new bytes(0)) ); } diff --git a/protocol/test/foundry/utils/BeanstalkDeployer.sol b/protocol/test/foundry/utils/BeanstalkDeployer.sol index 3f3c3d7bfc..5811b2daf6 100644 --- a/protocol/test/foundry/utils/BeanstalkDeployer.sol +++ b/protocol/test/foundry/utils/BeanstalkDeployer.sol @@ -20,7 +20,15 @@ import {MockConvertFacet, ConvertFacet} from "contracts/mocks/mockFacets/MockCon import {MockSeasonFacet, SeasonFacet} from "contracts/mocks/mockFacets/MockSeasonFacet.sol"; import {MockSiloFacet, SiloFacet} from "contracts/mocks/mockFacets/MockSiloFacet.sol"; import {MockPipelineConvertFacet, PipelineConvertFacet} from "contracts/mocks/mockFacets/MockPipelineConvertFacet.sol"; +import {MockFertilizerFacet, FertilizerFacet} from "contracts/mocks/mockFacets/MockFertilizerFacet.sol"; +import {MockWhitelistFacet, WhitelistFacet} from "contracts/mocks/mockFacets/MockWhitelistFacet.sol"; +import {MockFieldFacet, FieldFacet} from "contracts/mocks/mockFacets/MockFieldFacet.sol"; import {SeasonGettersFacet} from "contracts/beanstalk/sun/SeasonFacet/SeasonGettersFacet.sol"; +import {DiamondCutFacet} from "contracts/beanstalk/diamond/DiamondCutFacet.sol"; +import {IDiamondLoupe} from "contracts/interfaces/IDiamondLoupe.sol"; + +import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; +import "forge-std/console.sol"; /** * @title TestHelper @@ -60,7 +68,10 @@ contract BeanstalkDeployer is Utils { "SeasonFacet", // MockSeasonFacet "PipelineConvertFacet" // MockPipelineConvertFacet ]; - address[] facetAddresses; + address[] initialDeployFacetAddresses; + string[] initialDeploFacetNames; + address[] upgradeFacetAddresses; + string[] upgradeFacetNames; IDiamondCut.FacetCutAction[] cutActions; @@ -75,6 +86,39 @@ contract BeanstalkDeployer is Utils { vm.label(BEANSTALK, "Beanstalk"); // Create cuts. + setupFacetAddresses(mock, true, false); + + IDiamondCut.FacetCut[] memory cut = _multiCut( + initialDeploFacetNames, + initialDeployFacetAddresses, + cutActions + ); + d = deployDiamondAtAddress(deployer, BEANSTALK); + + // if mocking, set the diamond address to + // the canonical beanstalk address. + address initDiamondAddress; + if (mock) { + initDiamondAddress = address(new MockInitDiamond()); + } else { + initDiamondAddress = address(new InitDiamond()); + } + + vm.prank(deployer); + IDiamondCut(address(d)).diamondCut( + cut, + initDiamondAddress, + abi.encodeWithSignature("init()") + ); + + if (verbose) console.log("Diamond deployed at: ", address(d)); + } + + function setupFacetAddresses(bool mock, bool attack, bool includeUpgradeFacetsOnly) internal { + address[] memory facetAddresses = new address[](100); + string[] memory facetNames = new string[](100); + + uint256 facetCounter; // Facets that require external libraries need to be deployed by // `address(new Facet())` @@ -83,20 +127,38 @@ contract BeanstalkDeployer is Utils { // for facets with external libraries, deploy the facet, // rather than deploying using the bytecode. string memory facetName = facets[i]; + + if ( + includeUpgradeFacetsOnly && + (keccak256(abi.encodePacked(facetName)) == + keccak256(abi.encodePacked("OwnershipFacet")) || + keccak256(abi.encodePacked(facetName)) == + keccak256(abi.encodePacked("PauseFacet")) || + keccak256(abi.encodePacked(facetName)) == + keccak256(abi.encodePacked("DiamondCutFacet")) || + keccak256(abi.encodePacked(facetName)) == + keccak256(abi.encodePacked("DiamondCutFacet"))) + ) { + continue; + } + if (keccak256(abi.encode(facetName)) == keccak256(abi.encode("SeasonGettersFacet"))) { - facetAddresses.push(address(new SeasonGettersFacet())); + facetAddresses[facetCounter++] = address(new SeasonGettersFacet()); } else { - facetAddresses.push(address(deployCode(facetName))); + facetAddresses[facetCounter++] = address(deployCode(facetName)); } cutActions.push(IDiamondCut.FacetCutAction.Add); + // facetNames.push(facetName); + facetNames[facetCounter - 1] = facetName; } // Deploy mock only facets. - if (mock) { - facetAddresses.push(address(new MockAttackFacet())); - facets.push("MockAttackFacet"); + if (mock && attack) { + // facetAddresses.push(address(new MockAttackFacet())); + facetAddresses[facetCounter++] = address(new MockAttackFacet()); cutActions.push(IDiamondCut.FacetCutAction.Add); + facetNames[facetCounter - 1] = "MockAttackFacet"; } for (uint i; i < mockFacets.length; i++) { @@ -126,6 +188,24 @@ contract BeanstalkDeployer is Utils { } else { facetAddress = address(new SeasonFacet()); } + } else if (hashedName == keccak256(abi.encode("FertilizerFacet"))) { + if (mock) { + facetAddress = address(new MockFertilizerFacet()); + } else { + facetAddress = address(new FertilizerFacet()); + } + } else if (hashedName == keccak256(abi.encode("WhitelistFacet"))) { + if (mock) { + facetAddress = address(new MockWhitelistFacet()); + } else { + facetAddress = address(new WhitelistFacet()); + } + } else if (hashedName == keccak256(abi.encode("FieldFacet"))) { + if (mock) { + facetAddress = address(new MockFieldFacet()); + } else { + facetAddress = address(new FieldFacet()); + } } else if (hashedName == keccak256(abi.encode("SiloFacet"))) { if (mock) { facetAddress = address(new MockSiloFacet()); @@ -142,33 +222,25 @@ contract BeanstalkDeployer is Utils { facetAddress = address(deployCode(facet)); } - facetAddresses.push(facetAddress); - - // append the facet name to the facets array. - facets.push(facet); + facetAddresses[facetCounter++] = facetAddress; cutActions.push(IDiamondCut.FacetCutAction.Add); + facetNames[facetCounter - 1] = facet; } - IDiamondCut.FacetCut[] memory cut = _multiCut(facets, facetAddresses, cutActions); - d = deployDiamondAtAddress(deployer, BEANSTALK); - // if mocking, set the diamond address to - // the canonical beanstalk address. - address initDiamondAddress; - if (mock) { - initDiamondAddress = address(new MockInitDiamond()); - } else { - initDiamondAddress = address(new InitDiamond()); + // update array lengths + assembly { + mstore(facetAddresses, facetCounter) + mstore(facetNames, facetCounter) } - vm.prank(deployer); - IDiamondCut(address(d)).diamondCut( - cut, - initDiamondAddress, - abi.encodeWithSignature("init()") - ); - - if (verbose) console.log("Diamond deployed at: ", address(d)); + if (includeUpgradeFacetsOnly) { + upgradeFacetAddresses = facetAddresses; + upgradeFacetNames = facetNames; + } else { + initialDeployFacetAddresses = facetAddresses; + initialDeploFacetNames = facetNames; + } } /** @@ -210,6 +282,87 @@ contract BeanstalkDeployer is Utils { vm.stopPrank(); } + // useful for debugging which facets are erroring by adding logs to LibDiamond and deploying after forking + /*function upgradeDiamondFacet() internal { + string[] memory _facetNames = new string[](1); + _facetNames[0] = "DiamondCutFacet"; + address[] memory newFacetAddresses = new address[](1); + newFacetAddresses[0] = address(new DiamondCutFacet()); + + IDiamondCut.FacetCutAction[] memory facetCutActions = new IDiamondCut.FacetCutAction[](1); + facetCutActions[0] = IDiamondCut.FacetCutAction.Replace; + + // upgrade just the diamond cut facet + upgradeWithNewFacets( + BEANSTALK, // upgrading beanstalk. + IMockFBeanstalk(BEANSTALK).owner(), // fetch beanstalk owner. + _facetNames, + newFacetAddresses, + facetCutActions, + address(new EmptyInitContract()), // deploy the ReseedL2Migration. + abi.encodeWithSignature("init()"), // call init. + new bytes4[](0) + ); + }*/ + + /** + * @notice Forks mainnet at a given block, + */ + function forkMainnetAndUpgradeAllFacets(uint256 blockNumber) internal { + vm.createSelectFork(vm.envString("FORKING_RPC"), blockNumber); + + setupFacetAddresses(true, false, true); + + // upgradeDiamondFacet(); + + // the idea is to add/upgrade all the facets/mock facets that are in the constants at the top of this file + // get the list of all current selectors + IDiamondLoupe.Facet[] memory currentFacets = IDiamondLoupe(BEANSTALK).facets(); + bytes4[] memory currentSelectors = new bytes4[](1000); + uint256 selectorsCounter = 0; + for (uint256 i = 0; i < currentFacets.length; i++) { + // loop through all selectors in the facet + bytes4[] memory selectors = IDiamondLoupe(BEANSTALK).facetFunctionSelectors( + currentFacets[i].facetAddress + ); + for (uint256 j = 0; j < selectors.length; j++) { + // add the selector to the currentSelectors array + currentSelectors[selectorsCounter++] = selectors[j]; + } + } + assembly { + mstore(currentSelectors, selectorsCounter) + } + + // generated list of all the new facets + IDiamondLoupe.Facet[] memory newFacets = new IDiamondLoupe.Facet[]( + upgradeFacetAddresses.length + ); + + uint256 facetAddressesLength = upgradeFacetAddresses.length; + + bytes4[][] memory functionSelectorsArray = _generateMultiSelectors(upgradeFacetNames); + for (uint256 i = 0; i < upgradeFacetNames.length; i++) { + IDiamondLoupe.Facet memory facet = IDiamondLoupe.Facet( + upgradeFacetAddresses[i], + functionSelectorsArray[i] + ); + newFacets[i] = facet; + } + + assembly { + mstore(newFacets, facetAddressesLength) + } + + // generate the diamond cut required to upgrade all facets + IDiamondCut.FacetCut[] memory cut = generateDiamondCut(currentFacets, newFacets); + + vm.startPrank(IMockFBeanstalk(BEANSTALK).owner()); + // perform the diamond cut (upgrades Beanstalk) + IDiamondCut(BEANSTALK).diamondCut(cut, address(0), new bytes(0)); + vm.stopPrank(); + } + //////////////////////// Deploy ///////////////////////// /** @@ -291,7 +444,271 @@ contract BeanstalkDeployer is Utils { for (uint i = 0; i < _facetNames.length; i++) { cmd[i + 2] = _facetNames[i]; } + // be aware of cases where the response may be very large, which can break the EVM bytes memory res = vm.ffi(cmd); - selectorsArray = abi.decode(res, (bytes4[][])); + + if (res.length > 0) { + selectorsArray = _decodeCompactSelectors(res); + } else { + selectorsArray = new bytes4[][](0); + } + } + + function _decodeCompactSelectors(bytes memory data) internal pure returns (bytes4[][] memory) { + uint256 pointer = 0; + uint256 numContracts = uint256(bytes32(slice(data, pointer, 32))); + pointer += 32; + + bytes4[][] memory selectorsArray = new bytes4[][](numContracts); + + for (uint256 i = 0; i < numContracts; i++) { + uint16 numSelectors = uint16(bytes2(slice(data, pointer, 2))); + pointer += 2; + + bytes4[] memory selectors = new bytes4[](numSelectors); + for (uint256 j = 0; j < numSelectors; j++) { + selectors[j] = bytes4(slice(data, pointer, 4)); + pointer += 4; + } + + selectorsArray[i] = selectors; + } + + return selectorsArray; + } + + function generateDiamondCut( + IDiamondLoupe.Facet[] memory currentFacets, + IDiamondLoupe.Facet[] memory newFacets + ) internal pure returns (IDiamondCut.FacetCut[] memory) { + // Use a dynamic array for cuts + IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](0); + + // Create arrays to store all selectors and their corresponding new facet addresses + bytes4[] memory allSelectors = new bytes4[](0); + address[] memory allNewFacetAddresses = new address[](0); + + // Populate the arrays with data from newFacets + for (uint256 i = 0; i < newFacets.length; i++) { + for (uint256 j = 0; j < newFacets[i].functionSelectors.length; j++) { + allSelectors = appendToBytes4Array(allSelectors, newFacets[i].functionSelectors[j]); + allNewFacetAddresses = appendToAddressArray( + allNewFacetAddresses, + newFacets[i].facetAddress + ); + } + } + + // Process removals and replacements + for (uint256 i = 0; i < currentFacets.length; i++) { + bytes4[] memory selectorsToRemove = new bytes4[]( + currentFacets[i].functionSelectors.length + ); + bytes4[] memory selectorsToReplace = new bytes4[]( + currentFacets[i].functionSelectors.length + ); + uint256 removeCount = 0; + uint256 replaceCount = 0; + + for (uint256 j = 0; j < currentFacets[i].functionSelectors.length; j++) { + bytes4 selector = currentFacets[i].functionSelectors[j]; + (bool found, address newFacetAddress) = findNewFacetAddress( + selector, + allSelectors, + allNewFacetAddresses + ); + + if (!found) { + selectorsToRemove[removeCount] = selector; + removeCount++; + } else if (newFacetAddress != currentFacets[i].facetAddress) { + selectorsToReplace[replaceCount] = selector; + replaceCount++; + } + } + + if (removeCount > 0) { + bytes4[] memory finalSelectorsToRemove = new bytes4[](removeCount); + for (uint256 j = 0; j < removeCount; j++) { + finalSelectorsToRemove[j] = selectorsToRemove[j]; + } + cuts = appendToCuts( + cuts, + IDiamondCut.FacetCut( + address(0), + IDiamondCut.FacetCutAction.Remove, + finalSelectorsToRemove + ) + ); + } + + if (replaceCount > 0) { + bytes4[] memory finalSelectorsToReplace = new bytes4[](replaceCount); + for (uint256 j = 0; j < replaceCount; j++) { + finalSelectorsToReplace[j] = selectorsToReplace[j]; + } + (, address newFacetAddress) = findNewFacetAddress( + finalSelectorsToReplace[0], + allSelectors, + allNewFacetAddresses + ); + cuts = appendToCuts( + cuts, + IDiamondCut.FacetCut( + newFacetAddress, + IDiamondCut.FacetCutAction.Replace, + finalSelectorsToReplace + ) + ); + } + } + + // Process additions + for (uint256 i = 0; i < newFacets.length; i++) { + bytes4[] memory selectorsToAdd = new bytes4[](newFacets[i].functionSelectors.length); + uint256 addCount = 0; + + for (uint256 j = 0; j < newFacets[i].functionSelectors.length; j++) { + bytes4 selector = newFacets[i].functionSelectors[j]; + bool isNewSelector = true; + for (uint256 k = 0; k < currentFacets.length; k++) { + if (contains(currentFacets[k].functionSelectors, selector)) { + isNewSelector = false; + break; + } + } + if (isNewSelector) { + selectorsToAdd[addCount] = selector; + addCount++; + } + } + + if (addCount > 0) { + bytes4[] memory finalSelectorsToAdd = new bytes4[](addCount); + for (uint256 j = 0; j < addCount; j++) { + finalSelectorsToAdd[j] = selectorsToAdd[j]; + } + cuts = appendToCuts( + cuts, + IDiamondCut.FacetCut( + newFacets[i].facetAddress, + IDiamondCut.FacetCutAction.Add, + finalSelectorsToAdd + ) + ); + } + } + + return cuts; + } + + function findNewFacetAddress( + bytes4 selector, + bytes4[] memory allSelectors, + address[] memory allNewFacetAddresses + ) internal pure returns (bool, address) { + for (uint256 i = 0; i < allSelectors.length; i++) { + if (allSelectors[i] == selector) { + return (true, allNewFacetAddresses[i]); + } + } + return (false, address(0)); + } + + // these append functions are not ideal in terms for gas/performance, but they are convenient for testing + function appendToBytes4Array( + bytes4[] memory array, + bytes4 element + ) internal pure returns (bytes4[] memory) { + bytes4[] memory newArray = new bytes4[](array.length + 1); + for (uint256 i = 0; i < array.length; i++) { + newArray[i] = array[i]; + } + newArray[array.length] = element; + return newArray; + } + + function appendToAddressArray( + address[] memory array, + address element + ) internal pure returns (address[] memory) { + address[] memory newArray = new address[](array.length + 1); + for (uint256 i = 0; i < array.length; i++) { + newArray[i] = array[i]; + } + newArray[array.length] = element; + return newArray; + } + + function appendToCuts( + IDiamondCut.FacetCut[] memory cuts, + IDiamondCut.FacetCut memory newCut + ) internal pure returns (IDiamondCut.FacetCut[] memory) { + IDiamondCut.FacetCut[] memory newCuts = new IDiamondCut.FacetCut[](cuts.length + 1); + for (uint i = 0; i < cuts.length; i++) { + newCuts[i] = cuts[i]; + } + newCuts[cuts.length] = newCut; + return newCuts; + } + + function contains(bytes4[] memory array, bytes4 value) internal pure returns (bool) { + for (uint256 i = 0; i < array.length; i++) { + if (array[i] == value) { + return true; + } + } + return false; + } + + function _decodeDiamondCut( + bytes memory data + ) internal pure returns (IDiamondCut.FacetCut[] memory) { + uint256 pointer = 0; + uint256 numCuts = uint256(bytes32(slice(data, pointer, 32))); + pointer += 32; + + IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](numCuts); + + for (uint256 i = 0; i < numCuts; i++) { + address facetAddress = address(bytes20(slice(data, pointer, 20))); + pointer += 20; + + uint8 action = uint8(slice(data, pointer, 1)[0]); + pointer += 1; + + uint16 numSelectors = uint16(bytes2(slice(data, pointer, 2))); + pointer += 2; + + bytes4[] memory selectors = new bytes4[](numSelectors); + for (uint256 j = 0; j < numSelectors; j++) { + selectors[j] = bytes4(slice(data, pointer, 4)); + pointer += 4; + } + + cuts[i] = IDiamondCut.FacetCut( + facetAddress, + IDiamondCut.FacetCutAction(action), + selectors + ); + } + + return cuts; } + + function slice( + bytes memory data, + uint256 start, + uint256 length + ) internal pure returns (bytes memory) { + bytes memory result = new bytes(length); + for (uint256 i = 0; i < length; i++) { + result[i] = data[start + i]; + } + return result; + } +} + +contract EmptyInitContract { + function init() external {} } diff --git a/protocol/test/foundry/utils/OracleDeployer.sol b/protocol/test/foundry/utils/OracleDeployer.sol index 61d2cab398..0398796514 100644 --- a/protocol/test/foundry/utils/OracleDeployer.sol +++ b/protocol/test/foundry/utils/OracleDeployer.sol @@ -63,7 +63,10 @@ contract OracleDeployer is Utils { ////////// UNISWAP ////////// address constant WBTC_USDC_03_POOL = address(0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35); + address constant AAVE_ETH_03_POOL = address(0x5aB53EE1d50eeF2C1DD3d5402789cd27bB52c1bB); + address constant WSTETH_ETH_001_POOL = address(0x109830a1AAaD605BbF02a9dFA7B0B92EC2FB7dAa); address constant WBTC = address(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + address constant AAVE = address(0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9); // new uniswap pools should be appended here. address[][] public pools = [ diff --git a/protocol/test/hardhat/ChainlinkTwapOracle.test.js b/protocol/test/hardhat/ChainlinkTwapOracle.test.js index 31015bcd60..faaf33e6ec 100644 --- a/protocol/test/hardhat/ChainlinkTwapOracle.test.js +++ b/protocol/test/hardhat/ChainlinkTwapOracle.test.js @@ -17,13 +17,6 @@ async function setToSecondsAfterHour(seconds = 0) { await network.provider.send("evm_setNextBlockTimestamp", [hourTimestamp]); } -async function checkPriceWithError(price, error = "1000000") { - expect(await mockBeanstalk.getEthUsdPrice()).to.be.within( - to6(price).sub(toBN(error).div("2")), - to6(price).add(toBN(error).div("2")) - ); // Expected Rounding error -} - describe("TWAP Chainlink Oracle", function () { before(async function () { [owner, user, user2] = await ethers.getSigners(); diff --git a/yarn.lock b/yarn.lock index 300ba2338a..cc6988a6e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2082,7 +2082,7 @@ __metadata: resolution: "@beanstalk/protocol@workspace:protocol" dependencies: "@beanstalk/wells": "npm:0.4.1" - "@beanstalk/wells1.2": "npm:@beanstalk/wells@1.2.0-prerelease3" + "@beanstalk/wells1.2": "npm:@beanstalk/wells@1.2.0" "@ethereum-waffle/chai": "npm:4.0.10" "@nomicfoundation/hardhat-network-helpers": "npm:^1.0.10" "@nomiclabs/hardhat-ethers": "npm:^2.2.1" @@ -2245,10 +2245,10 @@ __metadata: languageName: unknown linkType: soft -"@beanstalk/wells1.2@npm:@beanstalk/wells@1.2.0-prerelease3": - version: 1.2.0-prerelease3 - resolution: "@beanstalk/wells@npm:1.2.0-prerelease3" - checksum: 10/b824397cbaa938f1526047965f921008f565bb8832011bf9768b69b378dd012529a2c621f537de84bd6a14791b8f675f388094b409185e87f010a8fc54cc06a0 +"@beanstalk/wells1.2@npm:@beanstalk/wells@1.2.0": + version: 1.2.0 + resolution: "@beanstalk/wells@npm:1.2.0" + checksum: 10/670516484f77de10e45ed30ebbbd8b076eeb70dd8a5e26950d3547bf7b2cd43713baa90fab44c7ac56d1f9e9be77d38883901d3aa1e34830eb6e1119ad3ba56f languageName: node linkType: hard