Closed howlbot-integration[bot] closed 2 months ago
The analysis that has been performed has been performed on the library in isolation without regard to the surrounding calling environment.
In the actual codebase the usage of the library does not involve calls with 0 sellAmount. This property is achieved by the early return here and the setting of the trade data here.
If the warden can present evidence that the broader codebase has the issue claimed that would change our minds.
function testPrepareTradeToCoverDeficit(uint192 buyAmount) public {
require(buyAmount >= minVolume, "Buy amount too small to trade");
TradeInfo memory tradeInfo = TradeInfo({
sell: IAsset(address(sellAsset)),
buy: IAsset(address(buyAsset)),
sellAmount: 0, // will be calculated
buyAmount: buyAmount,
prices: TradePrices({
sellLow: 1e18,
sellHigh: 2e18,
buyLow: 1e18,
buyHigh: 2e18
})
});
trade.sellAmount = fixMin(slippedSellAmount, trade.sellAmount); // {sellTok}
The sellAmount in the test is 0, which I think is the reason for the conclusion below.
Trade Amount Calculations: The exactSellAmount and slippedSellAmount calculations are producing non-zero values (2000288176520567114 and 2020493107596532439 respectively), but these aren't being reflected in the final sellAmount in the trade request, which is suspiciously 0.
Also, as the sponsor said, if warden can provide a test with full context, I will consider it again, but currently think it is invalid.
thereksfour marked the issue as unsatisfactory: Insufficient proof
Lines of code
https://github.com/reserve-protocol/protocol/blob/4.0.0/contracts/p1/mixins/TradeLib.sol#L148
Vulnerability details
Impact
Based on the provided echidna logs there is an issue within the TradeLib contract the prepareTradeToCoverDeficit function. Specifically, the Exact Sell Amount and Request Sell Amount are coming out as 0, which implies that the calculations for determining the sell amount aren't producing valid results.
Potential Causes:
Proof of Concept
echidna test: testPrepareTradeToCoverDeficit(uint192): failed!💥
Call sequence: TradeLibEchidnaTest3.testPrepareTradeToCoverDeficit(1000101419437632800)
Traces: emit LogUint(name=«Exact Sell Amount Calculation», value=2000202838875265600) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:78) emit LogUint(name=«Slipped Sell Amount Calculation», value=2020406907954813738) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:79) call MockAsset2::maxTradeVolume()() (/share/p1/mixins/TradeLib.sol:185) └╴← (1000000000000000000000000) call MockAsset2::maxTradeVolume()() (/share/p1/mixins/TradeLib.sol:185) └╴← (1000000000000000000000000) call MockAsset2::erc20Decimals()() (/share/p1/mixins/TradeLib.sol:80) └╴← (18) call MockAsset2::erc20Decimals()() (/share/p1/mixins/TradeLib.sol:81) └╴← (18) emit LogUint(name=«Buy Amount», value=1000101419437632800) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:87) emit LogUint(name=«Exact Sell Amount», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:88) emit LogUint(name=«Request Sell Amount», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:89) emit LogUint(name=«Request Min Buy Amount», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:90) emit LogUint(name=«Sell Low Price», value=1000000000000000000) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:91) emit LogUint(name=«Max Trade Slippage», value=10000000000000000) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:93) emit LogUint(name=«Is Not Dust Trade», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:94)
this is the echidna test that was used, // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19;
import "./MockAsset2.sol"; import "../../libraries/Fixed.sol"; import "../../interfaces/IAsset.sol"; import "../../echidna/IERC20.sol"; import "../../p1/mixins/TradeLib.sol"; import "../../echidna/open/IERC20Metadata.sol";
contract TradeLibEchidnaTest3 { using FixLib for uint192;
}
The bug identified in the prepareTradeToCoverDeficit function can have several significant consequences:
Tools Used
echidna and VS code
Recommended Mitigation Steps
We need to update the prepareTradeToCoverDeficit function in the TradeLib contract at line 148: Original function: function prepareTradeToCoverDeficit( TradeInfo memory trade, uint192 minTradeVolume, uint192 maxTradeSlippage ) internal view returns (bool notDust, TradeRequest memory req) { assert( trade.prices.sellLow != 0 && trade.prices.sellLow != FIX_MAX && trade.prices.buyHigh != 0 && trade.prices.buyHigh != FIX_MAX );
// line 148 insert change here }
Here is the Updated prepareTradeToCoverDeficit function, changes at line 148: function prepareTradeToCoverDeficit( TradeInfo memory trade, uint192 minTradeVolume, uint192 maxTradeSlippage ) internal view returns (bool notDust, TradeRequest memory req) { assert( trade.prices.sellLow != 0 && trade.prices.sellLow != FIX_MAX && trade.prices.buyHigh != 0 && trade.prices.buyHigh != FIX_MAX );
// Don't buy dust. trade.buyAmount = fixMax( trade.buyAmount, minTradeSize(minTradeVolume, trade.prices.buyHigh) );
// Calculate the exact and slipped sell amounts uint192 exactSellAmount = trade.buyAmount.mulDiv( trade.prices.buyHigh, trade.prices.sellLow, CEIL ); uint192 slippedSellAmount = exactSellAmount.div(FIX_ONE.minus(maxTradeSlippage), CEIL);
// Assign the calculated sell amount to trade.sellAmount trade.sellAmount = fixMin(slippedSellAmount, trade.sellAmount == 0 ? slippedSellAmount : trade.sellAmount); // {sellTok}
// Check if the trade is still considered dust notDust = trade.sellAmount > 0 && isEnoughToSell(trade.sell, trade.sellAmount, trade.prices.sellLow, minTradeVolume); // line 148 // Prepare the trade request if (notDust) { req = TradeRequest({ sell: trade.sell, buy: trade.buy, sellAmount: trade.sellAmount.shiftl_toUint(int8(trade.sell.erc20Decimals()), FLOOR), minBuyAmount: trade.sellAmount.mulDiv( trade.prices.sellLow, trade.prices.buyHigh, CEIL ).shiftl_toUint(int8(trade.buy.erc20Decimals()), CEIL) }); }
return (notDust, req); }
Explanation of the Updated Function:
The updated prepareTradeToCoverDeficit function includes essential changes to address the bug:
Proper Assignment to trade.sellAmount:
Refinement of the notDust Condition:
Summary of Changes:
After updating the tradeLib.sol contract, ALL echidna tests PASS:
testPrepareTradeToCoverDeficit(uint192): passing sellAsset(): passing maxSlippage(): passing buyAsset(): passing testPrepareTradeSell(uint192): passing minVolume(): passing AssertionFailed(..): passing
Conclusion: These updates ensure that the trade preparation logic within TradeLib operates as intended, preventing failed trades and maintaining the protocol's economic integrity. The successful passing of all Echidna tests after implementing these changes confirms the robustness of the fix.
Assessed type
Math