/// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
 /// @param shares LP shares being withdrawn
 /// @param totalShares total # of LP tokens in the vault
 /// @return t0 Token0 earned from burned liquidity + fees.
 /// Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
 /// @return t1 Token1 earned from burned liquidity + fees
 function \_burnAndCollect(
 uint256 shares,
 uint256 totalShares
 ) internal override returns (uint256 t0, uint256 t1) {
 // First, fetch current positions, Only tend() and withdraw() call this function,
 // and neither uses these positions elsewhere (tend uses updated ones).
 LiquidityPositions memory \_positions = positions;

 // For each position, burn() and then withdraw correct amount of liquidity.
 uint256 fees0;
 uint256 fees1;
 uint256 positionCount = \_positions.lowerTick.length;
 for (uint256 i; i != positionCount; ++i) {
 int24 lowerTick = \_positions.lowerTick[i];
 int24 upperTick = \_positions.upperTick[i];

 uint128 liquidityToBurn;

 // Get position liquidity. If we don't want all of it,
 // here is where we specify how much we want
 // (liquidity \* fraction of pool which is being withdrawn)
 // Slightly tortured logic here due to stack management
 {
 (uint128 totalPositionLiquidity, , , , ) = \_position(
 lowerTick,
 upperTick
 );

 // Shares is always >= totalShares so no risk of uint128 overflow here.
 liquidityToBurn = uint128(
 FullMath.mulDiv(
 totalPositionLiquidity,
 shares,
 totalShares
 )
 );
 }

 // amountsOwed are always pulled with liquidity in this contract,
 // so if position liquidity is 0, no need to withdraw anything.
 if (liquidityToBurn > 0) {
 // Amount burned in each position.
 // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
 (uint256 posBurned0, uint256 posBurned1) = pool.burn(
 lowerTick,
 upperTick,
 liquidityToBurn
 );

 // Collect all owed tokens including earned fees
 (uint256 collect0, uint256 collect1) = pool.collect(
 address(this),
 lowerTick,
 upperTick,
 type(uint128).max,
 type(uint128).max
 );

 /\*
 \* Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
 \* No need to check for overflow--values come from Uniswap, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
 \* Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of uniswap
 (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
 \*/
 t0 += posBurned0;
 t1 += posBurned1;

 // Fees earned by liquidity inside uniswap = collected - burned.
 // First we allocate some to steer and some to strategist.
 // The remainder is the fees earned by LPs.
 // So after that we add remainder \* shares / totalShares,
 // and that gives us t0 and t1 allocated to whatever's being withdrawn.

 // Since burned will never be greater than collected, no need to check for underflow here.
 // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
 fees0 += collect0 - posBurned0;
 fees1 += collect1 - posBurned1;
 }
 }

 // Emit fee info
 emit FeesEarned(fees0, fees1);

 // Update accrued protocol fees
 if (fees0 > 0) {
 uint256 totalCut0 = FullMath.mulDiv(fees0, TOTAL\_FEE, FEE\_DIVISOR);

 // Subtract fees going to strategist/steer from fees going to vault
 fees0 -= totalCut0;

 // Allocate fee amounts to strategist/steer correctly
 uint256 feesToSteer0 = FullMath.mulDiv(
 totalCut0,
 STEER\_FRACTION\_OF\_FEE,
 FEE\_DIVISOR
 );

 // Increase fees
 accruedSteerFees0 = accruedSteerFees0.add(feesToSteer0);
 // Since steer fees = (totalCut \* steerFraction) / 1e4,
 // and steerFraction cannot be greater than 1e4, no need to check for underflow here.
 accruedStrategistFees0 = accruedStrategistFees0.add(
 totalCut0 - feesToSteer0
 );
 }

 if (fees1 > 0) {
 uint256 totalCut1 = FullMath.mulDiv(fees1, TOTAL\_FEE, FEE\_DIVISOR);
 fees1 -= totalCut1;

 uint256 feesToSteer1 = FullMath.mulDiv(
 totalCut1,
 STEER\_FRACTION\_OF\_FEE,
 FEE\_DIVISOR
 );

 accruedSteerFees1 = accruedSteerFees1.add(feesToSteer1);
 accruedStrategistFees1 = accruedStrategistFees1.add(
 totalCut1 - feesToSteer1
 );
 }

 // Add fees earned by burned position to burned amount
 t0 = t0.add(FullMath.mulDiv(fees0, shares, totalShares));
 t1 = t1.add(FullMath.mulDiv(fees1, shares, totalShares));
 }
// @dev 모든 포지션에서 유동성을 인출하고 이 과정에서 수수료를 올바르게 할당합니다. /// @param shares 출금되는 LP 주식 수 /// @param total볼트에 있는 LP 토큰의 총 주식 수 /// @return t0 소각된 유동성 + 수수료로 얻은 토큰0. /// 소각 + 출금되는 LP 지분에 해당하는 수수료만 포함 (경향의 경우 100%) /// @return t1 소각된 유동성 + 수수료로 획득한 토큰1 함수 \_burnAndCollect( uint256 주식 uint256 총주식 ) 내부 오버라이드 반환 (uint256 t0, uint256 t1) { // 먼저 현재 포지션을 가져옵니다. 경향()과 철회()만 이 함수를 호출합니다, // 다른 곳에서는 이 포지션을 사용하지 않습니다(경향은 업데이트된 포지션을 사용합니다). 유동성포지션 메모리 \_포지션 = 포지션; // 각 포지션에 대해 소각()한 다음 정확한 양의 유동성을 인출합니다. uint256 수수료0; uint256 fees1; uint256 positionCount = \_positions.lowerTick.length; for (uint256 i; i != positionCount; ++i) { int24 lowerTick = \_positions.lowerTick[i]; int24 upperTick = \_positions.upperTick[i]; uint128 liquidityToBurn; // 포지션 유동성을 가져옵니다. 모든 유동성을 원하지 않는다면, // 여기서 원하는 양을 지정합니다. // (유동성 \* 인출되는 풀의 일부분) // 스택 관리로 인해 로직이 약간 복잡해졌습니다. { (uint128 totalPositionLiquidity, , , , ) = \_position( lowerTick, upperTick ); // 주식은 항상 >= 총주식이므로 여기서 uint128이 오버플로될 위험이 없습니다. 유동성소각 = uint128( FullMath.mulDiv( totalPositionLiquidity, shares, totalShares ) ); } // 이 컨트랙트에서 amountsOwed는 항상 유동성과 함께 인출됩니다, // 따라서 포지션 유동성이 0이면 아무것도 인출할 필요가 없습니다. if (liquidityToBurn > 0) { // 각 포지션에서 소각된 금액입니다. // 인출된 유동성 금액에 해당합니다(즉, 수수료는 포함되지 않음). (uint256 posBurned0, uint256 posBurned1) = pool.burn( lowerTick, upperTick, liquidityToBurn ); // 수익 수수료를 포함한 모든 미지급 토큰을 회수합니다. (uint256 collect0, uint256 collect1) = pool.collect( 주소(이), lowerTick, upperTick, type(uint128).max, type(uint128).max ); /\* \* t0과 t1에 소각된 유동성을 추가합니다 -- 이는 이미 인출되는 amt에 비례합니다. \* 오버플로우를 확인할 필요 없음-값은 유니스왑에서 제공되며, 총 소각량이 2^256 - 1보다 크면 토큰의 총 공급량보다 더 많이 소각된 것을 의미합니다. \* 기술적으로는 가능하지만, 유니스왑의 특성상 악의적인 토큰이 이미 볼트 보유량을 탈취할 수 있습니다. (볼트에 모든 토큰을 예치한 다음 임의의 양의 악성 토큰을 발행하고 실제 토큰으로 스왑하면 됩니다). \*/ t0 += posBurned0; t1 += posBurned1; // 유니스왑 내부의 유동성으로 벌어들인 수수료 = 수집 - 소각. // 먼저 일부는 운영자에게, 일부는 전략가에게 할당합니다. // 나머지는 LP가 벌어들인 수수료입니다. // 그 후 나머지 \* 주식 / 총 주식 수를 더합니다, // 그러면 인출되는 금액에 할당된 T0와 T1이 나옵니다. // 소각된 양이 수집된 양보다 많지 않으므로 여기서 언더플로우를 확인할 필요가 없습니다. // 수집 값은 원래 uint128이므로 오버플로를 확인할 필요도 없습니다. 오버플로가 발생하려면 최대 ~2^128을 더해야 합니다. fees0 += collect0 - posBurned0; fees1 += collect1 - posBurned1; } } // 수수료 정보 내보내기 emit FeesEarned(fees0, fees1); // 발생한 프로토콜 수수료 업데이트 if (fees0 > 0) { uint256 totalCut0 = FullMath.mulDiv(fees0, TOTAL\_FEE, FEE\_DIVISOR); // 금고로 가는 수수료에서 전략가/스티어에게 가는 수수료를 뺍니다. fees0 -= totalCut0; // 전략가/스티어에게 수수료 금액을 올바르게 할당합니다. uint256 feesToSteer0 = FullMath.mulDiv( totalCut0, STEER\_FRACTION\_OF\_FEE, FEE\_DIVISOR ); // 수수료 증가 accruedSteerFees0 = accruedSteerFees0.add(feesToSteer0); // 조종사 수수료 = (총절감 \* 조종사 수수료) / 1e4, // 그리고 steerFraction은 1e4보다 클 수 없으므로 여기서 언더플로우를 확인할 필요가 없습니다. accruedStrategistFees0 = accruedStrategistFees0.add( totalCut0 - feesToSteer0 ); } if (fees1 > 0) { uint256 totalCut1 = FullMath.mulDiv(fees1, TOTAL\_FEE, FEE\_DIVISOR); fees1 -= totalCut1; uint256 feesToSteer1 = FullMath.mulDiv( totalCut1, STEER\_FRACTION\_OF\_FEE, FEE\_DIVISOR ); accruedSteerFees1 = accruedSteerFees1.add(feesToSteer1); accruedStrategistFee1 = accruedStrategistFee1.add( totalCut1 - feesToSteer1 ); } // 소각된 포지션에서 발생한 수수료를 소각 금액에 추가합니다. t0 = t0.add(FullMath.mulDiv(fees0, shares, totalShares)); t1 = t1.add(FullMath.mulDiv(fees1, shares, totalShares)); }