Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Liquidity calculation fix #1165

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions protocol/contracts/ecosystem/price/BeanstalkPrice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,33 @@ 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;
p.deltaB += p.ps[i].deltaB;
}
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);
}
}
2 changes: 2 additions & 0 deletions protocol/contracts/ecosystem/price/P.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ contract P {
uint256[2] balances;
uint256 price;
uint256 liquidity;
uint256 beanLiquidity;
uint256 nonBeanLiquidity;
int256 deltaB;
uint256 lpUsd;
uint256 lpBdv;
Expand Down
17 changes: 13 additions & 4 deletions protocol/contracts/ecosystem/price/WellPrice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ contract WellPrice {
uint256[2] balances;
uint256 price;
uint256 liquidity;
uint256 beanLiquidity;
uint256 nonBeanLiquidity;
int256 deltaB;
uint256 lpUsd;
uint256 lpBdv;
Expand All @@ -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)
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions protocol/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ gas_reports = ['*']
# Cache to `$HOME/.foundry/cache/<chain id>/<block number>`.
no_storage_caching = false
no_match_test = "testFork"
no_match_contract = "Reseed|L1ReceiverFacetForkTest|L1ReceiverFacetTest|ReseedL1ReceiverFacetForkTest|ReseedFunctionalityTest"

[profile.differential]
match_test = "testDiff"
Expand Down
3 changes: 2 additions & 1 deletion protocol/test/foundry/Migration/ReseedState.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
149 changes: 149 additions & 0 deletions protocol/test/foundry/silo/Oracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 4 additions & 5 deletions protocol/test/foundry/utils/BasinDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading