sherlock-2025-04-burve-h08

[H-08] ValueFacet.removeValueSingle will withdraw less than required from the vertex vault due to unaccounted tax

보고서

Summary

유저가 출금을 할 때 수수료를 지불해야 한다. 수수료를 제거한 금액을 계산한 뒤 그 금액에서 중복으로 수수료를 제거하였다. 따라서 유저는 수수료를 2배 지불하고 실제로 받아야 하는 것보다 적은 토큰을 받는다.

Keyword

fee, arithmetic error, logic flaw

Vulnerability

유저는 엔트리포인트인 ValueFacet.removeValueSingle 함수를 호출해 출금을 요청한다. 이 함수에서는 Closure.removeValueSingle 를 호출하여 출금될 토큰 양과 수수료의 양을 계산한다. 여기서 리턴값 removedNominal 는 수수료를 제외한 출금액을 의미한다. 그런데 이후 realTax 만큼을 중복 제외한다. 즉, 수수료를 2배 지불하는 것과 마찬가지이다.

function removeValueSingle(
    address recipient,
    uint16 _closureId,
    uint128 value,
    uint128 bgtValue,
    address token,
    uint128 minReceive
) external nonReentrant returns (uint256 removedBalance) {
    ...
@>  (uint256 removedNominal, uint256 nominalTax) = c.removeValueSingle(
        value,
        bgtValue,
        vid
    );
    uint256 realRemoved = AdjustorLib.toReal(token, removedNominal, false);
    Store.vertex(vid).withdraw(cid, realRemoved, false);
@>  uint256 realTax = FullMath.mulDiv(
        removedBalance,
        nominalTax,
        removedNominal
    );
    c.addEarnings(vid, realTax);
@>  removedBalance = realRemoved - realTax; // How much the user actually gets.
    require(removedBalance >= minReceive, PastSlippageBounds());
@>  TransferHelper.safeTransfer(token, recipient, removedBalance);
}

다음은 Closure.removeValueSingle 함수이다. 리턴값 removedAmount는 수수료를 제외한 금액임을 확인할 수 있다.

function removeValueSingle(
    Closure storage self,
    uint256 value,
    uint256 bgtValue,
    VertexId vid
) internal returns (uint256 removedAmount, uint256 tax) {
    ...
    // We first calculate what value is effectively "added" by not removing the tokens.
    // And then we make sure to remove that amount of value with the out token.
    uint256 fairVBalance = iterSingleValueDiff(self, valIter, false);
@>  removedAmount = self.balances[valIter.vIdx] - fairVBalance;
    // Now we have the addedValue which we can remove, and the fair balance for our vertex.
 
    ...
    tax = FullMath.mulX128(untaxedRemove, self.baseFeeX128, true);
@>  removedAmount += untaxedRemove - tax;
    // This needs to happen last.
    self.valueStaked -= value;
    self.bgtValueStaked -= bgtValue;
}

Impact

유저가 실제로 받아야 하는 것보다 적은 금액을 출금받는다.

Mitigation

수수료를 중복으로 떼지 않도록 어느 한쪽을 삭제한다.


tags: bughunting, burve, smart contract, solidity, severity high, fee, arithmetic error, logic flaw