diff --git a/protocol/contracts/ecosystem/price/BeanstalkPrice.sol b/protocol/contracts/ecosystem/price/BeanstalkPrice.sol index 6e3292970..8d295cd1d 100644 --- a/protocol/contracts/ecosystem/price/BeanstalkPrice.sol +++ b/protocol/contracts/ecosystem/price/BeanstalkPrice.sol @@ -23,12 +23,19 @@ contract BeanstalkPrice is WellPrice { **/ function price() external view returns (Prices memory p) { address[] memory wells = beanstalk.getWhitelistedWellLpTokens(); + return priceForWells(wells); + } + + /** + * @notice Returns the non-manipulation resistant on-chain liquidiy, deltaB and price data for + * Bean for the passed in wells. + * @dev No protocol should use this function to calculate manipulation resistant Bean price data. + **/ + function priceForWells(address[] memory wells) public view returns (Prices memory p) { p.ps = new P.Pool[](wells.length); for (uint256 i = 0; i < wells.length; i++) { p.ps[i] = getWell(wells[i]); } - - // assumes that liquidity and prices on all pools uses the same precision. for (uint256 i = 0; i < p.ps.length; i++) { p.price += p.ps[i].price.mul(p.ps[i].liquidity); p.liquidity += p.ps[i].liquidity; @@ -36,4 +43,13 @@ contract BeanstalkPrice is WellPrice { } p.price = p.price.div(p.liquidity); } + + /** + * @notice Returns the non-manipulation resistant on-chain liquidiy, deltaB and price data for + * Bean in the specified liquidity pools. + * @dev No protocol should use this function to calculate manipulation resistant Bean price data. + **/ + function poolPrice(address pool) public view returns (P.Pool memory p) { + return getWell(pool); + } } diff --git a/protocol/contracts/ecosystem/price/P.sol b/protocol/contracts/ecosystem/price/P.sol index 64ff39fa2..a6c2a848d 100644 --- a/protocol/contracts/ecosystem/price/P.sol +++ b/protocol/contracts/ecosystem/price/P.sol @@ -8,6 +8,8 @@ contract P { uint256[2] balances; uint256 price; uint256 liquidity; + uint256 beanLiquidity; + uint256 nonBeanLiquidity; int256 deltaB; uint256 lpUsd; uint256 lpBdv; diff --git a/protocol/contracts/ecosystem/price/WellPrice.sol b/protocol/contracts/ecosystem/price/WellPrice.sol index a84751896..3fe65189e 100644 --- a/protocol/contracts/ecosystem/price/WellPrice.sol +++ b/protocol/contracts/ecosystem/price/WellPrice.sol @@ -43,6 +43,8 @@ contract WellPrice { uint256[2] balances; uint256 price; uint256 liquidity; + uint256 beanLiquidity; + uint256 nonBeanLiquidity; int256 deltaB; uint256 lpUsd; uint256 lpBdv; @@ -67,9 +69,9 @@ contract WellPrice { uint256 beanIndex = beanstalk.getBeanIndex(wellTokens); uint256 tknIndex = beanIndex == 0 ? 1 : 0; - // swap 1 bean of the opposite asset to get the usd price + // swap 1 bean of the opposite asset to get the bean price // price = amtOut/tknOutPrice - uint256 assetPrice = beanstalk.getUsdTokenPrice(pool.tokens[tknIndex]); + uint256 assetPrice = beanstalk.getUsdTokenPrice(pool.tokens[tknIndex]); // $1 gets assetPrice worth of tokens if (assetPrice > 0) { pool.price = well .getSwapOut(wellTokens[beanIndex], wellTokens[tknIndex], 1e6) @@ -78,8 +80,15 @@ contract WellPrice { } // liquidity is calculated by getting the usd value of the bean portion of the pool, - // and multiplying by 2 to get the total liquidity of the pool. - pool.liquidity = pool.balances[beanIndex].mul(pool.price).mul(2).div(PRICE_PRECISION); + // and the usd value of the non-bean portion of the pool. + + pool.beanLiquidity = pool.balances[beanIndex].mul(pool.price).div(PRICE_PRECISION); + pool.nonBeanLiquidity = WELL_DECIMALS.div(assetPrice).mul(pool.balances[tknIndex]).div( + PRICE_PRECISION * PRICE_PRECISION + ); + + pool.liquidity = pool.beanLiquidity.add(pool.nonBeanLiquidity); + // attempt to get deltaB, if it fails, set deltaB to 0. try beanstalk.poolCurrentDeltaB(wellAddress) returns (int256 deltaB) { pool.deltaB = deltaB; diff --git a/protocol/foundry.toml b/protocol/foundry.toml index 86689af9b..7f15b058d 100644 --- a/protocol/foundry.toml +++ b/protocol/foundry.toml @@ -42,6 +42,7 @@ gas_reports = ['*'] # Cache to `$HOME/.foundry/cache//`. no_storage_caching = false no_match_test = "testFork" +no_match_contract = "Reseed|L1ReceiverFacetForkTest|L1ReceiverFacetTest|ReseedL1ReceiverFacetForkTest|ReseedFunctionalityTest" [profile.differential] match_test = "testDiff" diff --git a/protocol/test/foundry/Migration/ReseedState.t.sol b/protocol/test/foundry/Migration/ReseedState.t.sol index e53bbbfb4..804e4c377 100644 --- a/protocol/test/foundry/Migration/ReseedState.t.sol +++ b/protocol/test/foundry/Migration/ReseedState.t.sol @@ -111,7 +111,8 @@ contract ReseedStateTest is TestHelper { IMockFBeanstalk.AssetSettings memory assetSettings = l2Beanstalk.tokenSettings(L2BEAN); // log milestone stem and season - console.log("Milestone stem: ", assetSettings.milestoneStem); + console.log("Milestone stem: "); + console.logInt(assetSettings.milestoneStem); console.log("Milestone season: ", assetSettings.milestoneSeason); } diff --git a/protocol/test/foundry/silo/Oracle.t.sol b/protocol/test/foundry/silo/Oracle.t.sol index e16beca90..20b35a5ae 100644 --- a/protocol/test/foundry/silo/Oracle.t.sol +++ b/protocol/test/foundry/silo/Oracle.t.sol @@ -11,6 +11,7 @@ import {LibChainlinkOracle} from "contracts/libraries/Oracle/LibChainlinkOracle. import {IMockFBeanstalk} from "contracts/interfaces/IMockFBeanstalk.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IWell} from "contracts/interfaces/basin/IWell.sol"; +import "contracts/ecosystem/price/BeanstalkPrice.sol"; import "forge-std/console.sol"; /** @@ -364,6 +365,154 @@ contract OracleTest is TestHelper { assertEq(deltaB, 0); } + function testBeanstalkPriceContract() public { + address payable L2_BEANSTALK = payable(address(0xD1A0060ba708BC4BCD3DA6C37EFa8deDF015FB70)); + // fork arbitrum mainnet + vm.createSelectFork(vm.envString("ARBITRUM_FORKING_RPC"), 267500000); + + // deploy beanstalk price contract + address beanstalkPrice = address(new BeanstalkPrice(L2_BEANSTALK)); + + { + // call get Pool price for 0xBea00ee04D8289aEd04f92EA122a96dC76A91bd7, the bean/usdc pool on arbitrum + P.Pool memory price = BeanstalkPrice(beanstalkPrice).poolPrice( + 0xBea00ee04D8289aEd04f92EA122a96dC76A91bd7 + ); + // as of block 267500000, USDC is worth 1000123 and bean is worth 446792 usdc + // liquidity in the pool as of this block is the following: + assertEq(price.price, 0.446792e6, "bean price from usdc pool"); // $0.44 + assertEq(price.liquidity, 58462.524718e6, "liquidity from usdc pool"); // $58k + assertEq(price.beanLiquidity, 41275.454618e6, "bean liquidity from usdc pool"); // $41k + assertEq(price.nonBeanLiquidity, 17187.070100e6, "usdc liquidity from usdc pool"); // $17k + } + + { + P.Pool memory price = BeanstalkPrice(beanstalkPrice).poolPrice( + 0xBEa00BbE8b5da39a3F57824a1a13Ec2a8848D74F // the bean/wsteth pool on arbitrum + ); + assertEq(price.price, 0.449594e6, "bean price from wsteth pool"); // $0.44 + assertEq(price.liquidity, 12939952.763734e6, "liquidity from wsteth pool"); // $12.9m + assertEq(price.beanLiquidity, 6470568.833889e6, "bean liquidity from wsteth pool"); // $6.4m + assertEq(price.nonBeanLiquidity, 6469383.929845e6, "wsteth liquidity from wsteth pool"); // $6.4m + } + { + P.Pool memory price = BeanstalkPrice(beanstalkPrice).poolPrice( + 0xBea00DDe4b34ACDcB1a30442bD2B39CA8Be1b09c // the bean/wbtc pool on arbitrum + ); + assertEq(price.price, 0.464261e6, "bean price from wbtc pool"); // $0.46 + assertEq(price.liquidity, 20.973293e6, "liquidity from wbtc pool"); // $20 + assertEq(price.beanLiquidity, 10.253756e6, "bean liquidity from wbtc pool"); // $10.25 + assertEq(price.nonBeanLiquidity, 10.719537e6, "wbtc liquidity from wbtc pool"); // $10.71 + } + + // also test price() which returns all pools + { + BeanstalkPrice.Prices memory price = BeanstalkPrice(beanstalkPrice).price(); + for (uint256 i = 0; i < price.ps.length; i++) { + if (price.ps[i].pool == 0xBea00ee04D8289aEd04f92EA122a96dC76A91bd7) { + // verify liquidity in bean/usdc pool + assertEq(price.ps[i].liquidity, 58462.524718e6); + assertEq(price.ps[i].beanLiquidity, 41275.454618e6); + assertEq(price.ps[i].nonBeanLiquidity, 17187.070100e6); + } + if (price.ps[i].pool == 0xBea00DDe4b34ACDcB1a30442bD2B39CA8Be1b09c) { + // verify liquidity in bean/usdc pool + assertEq(price.ps[i].price, 0.464261e6, "bean price from wbtc pool"); + assertEq(price.ps[i].liquidity, 20.973293e6, "liquidity from wbtc pool"); + assertEq( + price.ps[i].beanLiquidity, + 10.253756e6, + "bean liquidity from wbtc pool" + ); + assertEq( + price.ps[i].nonBeanLiquidity, + 10.719537e6, + "wbtc liquidity from wbtc pool" + ); + } + if (price.ps[i].pool == 0xBEa00BbE8b5da39a3F57824a1a13Ec2a8848D74F) { + assertEq(price.ps[i].price, 0.449594e6, "bean price from wsteth pool"); + assertEq( + price.ps[i].liquidity, + 12939952.763734e6, + "liquidity from wsteth pool" + ); + assertEq( + price.ps[i].beanLiquidity, + 6470568.833889e6, + "bean liquidity from wsteth pool" + ); + assertEq( + price.ps[i].nonBeanLiquidity, + 6469383.929845e6, + "wsteth liquidity from wsteth pool" + ); + } + } + } + + // test priceForWells + { + address[] memory wells = new address[](2); + wells[0] = BEAN_USDC_WELL; + wells[1] = BEAN_WBTC_WELL; + + BeanstalkPrice.Prices memory price = BeanstalkPrice(beanstalkPrice).priceForWells( + wells + ); + console.log("priceForWells"); + console.log(price.price); + console.log(price.liquidity); + console.logInt(price.deltaB); + assertEq(price.price, 0.446798e6, "bean price from price for wells"); // $0.44 + assertEq(price.liquidity, 58483498011, "bean liq from price for wells"); // $0.44 + assertEq(price.deltaB, -43649902941, "deltaB from price for wells"); // $0.44 + + // check individual well prices + assertEq( + price.ps[0].price, + 0.446792e6, + "bean price from usdc pool from price for wells" + ); // $0.44 + assertEq( + price.ps[0].liquidity, + 58462.524718e6, + "liquidity from usdc pool from price for wells" + ); + assertEq( + price.ps[0].beanLiquidity, + 41275.454618e6, + "beanLiquidity from usdc pool from price for wells" + ); + assertEq( + price.ps[0].nonBeanLiquidity, + 17187.070100e6, + "nonBeanLiquidity from usdc pool from price for wells" + ); + + assertEq( + price.ps[1].price, + 0.464261e6, + "bean price from wbtc pool from price for wells" + ); + assertEq( + price.ps[1].liquidity, + 20.973293e6, + "liquidity from wbtc pool from price for wells" + ); + assertEq( + price.ps[1].beanLiquidity, + 10.253756e6, + "bean liquidity from wbtc pool from price for wells" + ); + assertEq( + price.ps[1].nonBeanLiquidity, + 10.719537e6, + "wbtc liquidity from wbtc pool from price for wells" + ); + } + } + //////////// Helper Functions //////////// function setupUniswapWBTCOracleImplementation() public { diff --git a/protocol/test/foundry/utils/BasinDeployer.sol b/protocol/test/foundry/utils/BasinDeployer.sol index dc4208956..6f9bac54a 100644 --- a/protocol/test/foundry/utils/BasinDeployer.sol +++ b/protocol/test/foundry/utils/BasinDeployer.sol @@ -41,11 +41,10 @@ contract BasinDeployer is Utils { address constant MFP = address(0xBA510f10E3095B83a0F33aa9ad2544E22570a87C); address constant WELL_IMPLMENTATION = address(0xBA510e11eEb387fad877812108a3406CA3f43a4B); - // extra wells addreses (used for convert testing) - // addresses were randomly generated and are not on-chain. - address constant BEAN_USDC_WELL = address(0x4444F7394455A8d1af37E8BEa52F2FCf6D39f158); - address constant BEAN_USDT_WELL = address(0x55554AF7c7CEe28994c7484C364768620C726D68); - address constant BEAN_WBTC_WELL = address(0x7777F3d631f856b4738Da79E0f4c10EE25C75B31); + // addresses reflect wells deployed on arbitrum + address constant BEAN_USDC_WELL = address(0xBea00ee04D8289aEd04f92EA122a96dC76A91bd7); + address constant BEAN_USDT_WELL = address(0xbEA00fF437ca7E8354B174339643B4d1814bED33); + address constant BEAN_WBTC_WELL = address(0xBea00DDe4b34ACDcB1a30442bD2B39CA8Be1b09c); string constant BEAN_WETH_WELL_NAME = "BEAN:WETH Constant Product 2 Well"; string constant BEAN_WETH_WELL_SYMBOL = "BEANWETHCP2w";