Skip to content

Commit

Permalink
Fix liquidity calculations for stableswap wells
Browse files Browse the repository at this point in the history
  • Loading branch information
pizzaman1337 committed Oct 25, 2024
1 parent bcb1154 commit 5c70f17
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 5 deletions.
9 changes: 9 additions & 0 deletions protocol/contracts/ecosystem/price/BeanstalkPrice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,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);
}
}
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
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
88 changes: 88 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,93 @@ 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"
);
}
}
}
}

//////////// Helper Functions ////////////

function setupUniswapWBTCOracleImplementation() public {
Expand Down

0 comments on commit 5c70f17

Please sign in to comment.