Platform: Code4rena
Start Date: 27/01/2022
Pot Size: $75,000 USDT
Total HM: 6
Participants: 29
Period: 7 days
Judge: leastwood
Total Solo HM: 6
Id: 72
League: ETH
Rank: 4/29
Findings: 3
Award: $5,395.56
🌟 Selected for report: 2
🚀 Solo Findings: 1
🌟 Selected for report: WatchPug
4882.8125 USDT - $4,882.81
WatchPug
function uniClassSell(DexInfo memory dexInfo, address buyToken, address sellToken, uint sellAmount, uint minBuyAmount, address payer, address payee ) internal returns (uint buyAmount){ address pair = getUniClassPair(buyToken, sellToken, dexInfo.factory); IUniswapV2Pair(pair).sync(); (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); sellAmount = transferOut(IERC20(sellToken), payer, pair, sellAmount); uint balanceBefore = IERC20(buyToken).balanceOf(payee); dexInfo.fees = getPairFees(dexInfo, pair); if (buyToken < sellToken) { buyAmount = getAmountOut(sellAmount, token1Reserves, token0Reserves, dexInfo.fees); IUniswapV2Pair(pair).swap(buyAmount, 0, payee, ""); } else { buyAmount = getAmountOut(sellAmount, token0Reserves, token1Reserves, dexInfo.fees); IUniswapV2Pair(pair).swap(0, buyAmount, payee, ""); } require(buyAmount >= minBuyAmount, 'buy amount less than min'); uint bought = IERC20(buyToken).balanceOf(payee).sub(balanceBefore); return bought; }
While uniClassBuy()
correctly checks the actually received amount by comparing the before and after the balance of the receiver, uniClassSell()
trusted the result given by getAmountOut()
. This makes uniClassSell()
can result in an output amount fewer than minBuyAmount
.
Change to:
function uniClassSell(DexInfo memory dexInfo, address buyToken, address sellToken, uint sellAmount, uint minBuyAmount, address payer, address payee ) internal returns (uint bought){ address pair = getUniClassPair(buyToken, sellToken, dexInfo.factory); IUniswapV2Pair(pair).sync(); (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); sellAmount = transferOut(IERC20(sellToken), payer, pair, sellAmount); uint balanceBefore = IERC20(buyToken).balanceOf(payee); dexInfo.fees = getPairFees(dexInfo, pair); if (buyToken < sellToken) { buyAmount = getAmountOut(sellAmount, token1Reserves, token0Reserves, dexInfo.fees); IUniswapV2Pair(pair).swap(buyAmount, 0, payee, ""); } else { buyAmount = getAmountOut(sellAmount, token0Reserves, token1Reserves, dexInfo.fees); IUniswapV2Pair(pair).swap(0, buyAmount, payee, ""); } uint bought = IERC20(buyToken).balanceOf(payee).sub(balanceBefore); require(bought >= minBuyAmount, 'buy amount less than min'); }
#0 - 0xleastwood
2022-02-25T13:16:01Z
Agree with this finding! Uniswap token swaps are meant to support all types of tokens. It does seem possible for there to be payer
to experience increased slippage because the check operates on getAmountOut()
and not the bought
output.
#1 - 0xleastwood
2022-02-25T13:17:18Z
It's fair to say that this will lead to value leakage, so I think medium
severity is justified.
WatchPug
constructor(OLEToken token_, address[] memory beneficiaries, uint256[] memory amounts, uint128[] memory startTimes, uint128[] memory endTimes) { require(beneficiaries.length == amounts.length && beneficiaries.length == startTimes.length && beneficiaries.length == endTimes.length, "Array length must be same"); token = token_; for (uint i = 0; i < beneficiaries.length; i++) { address beneficiary = beneficiaries[i]; releaseVars[beneficiary] = ReleaseVar(amounts[i], startTimes[i], endTimes[i], startTimes[i]); } }
function releaseAbleAmount(address beneficiary) public view returns (uint256){ ReleaseVar memory releaseVar = releaseVars[beneficiary]; require(block.timestamp >= releaseVar.startTime, "not time to unlock"); require(releaseVar.amount > 0, "beneficiary does not exist"); uint256 calTime = block.timestamp > releaseVar.endTime ? releaseVar.endTime : block.timestamp; return calTime.sub(releaseVar.lastUpdateTime).mul(releaseVar.amount) .div(releaseVar.endTime - releaseVar.startTime); }
releaseAbleAmount
will throw when releaseVar.endTime <= releaseVar.startTime
due to div by 0 or overlfow in SafeMath.
Check if releaseVar.endTime > releaseVar.startTime
when adding them can avoid unnecessary reverts later.
Consider adding checks for input validation of releaseVar.endTime > releaseVar.startTime
.
#0 - ColaM12
2022-02-02T19:30:52Z
Duplicate to #160
10.3004 USDT - $10.30
WatchPug
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.
Caching the array length in the stack saves around 3 gas per iteration.
Instances include:
OpenLevV1.sol#initialize()
ControllerV1.sol#distributeExtraRewards2Markets()
ControllerV1.sol#getSupplyRewards()
BscDexAggregatorV1.sol#setDexInfo()
EthDexAggregatorV1.sol#setDexInfo()
UniV3Dex.sol#uniV3SellMul()
FarmingPools.sol#getRewards()
FarmingPools.sol#initDistributions()
FarmingPools.sol#notifyRewardAmounts()
#0 - ColaM12
2022-02-02T19:04:03Z
Duplicate to #15
WatchPug
Avoiding unnecessary copying of data to memory for external calls can save gas.
function getRewards(address[] memory stakeTokens) external { for (uint256 i = 0; i < stakeTokens.length; i++) { getReward(stakeTokens[i]); } }
Can be changed to:
function getRewards(address[] calldata stakeTokens) external { for (uint256 i = 0; i < stakeTokens.length; i++) { getReward(stakeTokens[i]); } }
Other examples include:
#0 - ColaM12
2022-02-02T19:05:53Z
Duplicate to #29
WatchPug
For the storage variables that will be accessed multiple times, cache them in the stack can save ~100 gas from each extra read (SLOAD
after Berlin).
For example:
function lastTimeRewardApplicable(address stakeToken) public view returns (uint64) { return block.timestamp > distributions[stakeToken].periodFinish ? distributions[stakeToken].periodFinish : (uint64)(block.timestamp); }
Can be changed to:
function lastTimeRewardApplicable(address stakeToken) public view returns (uint64) { uint64 periodFinish = distributions[stakeToken].periodFinish; return block.timestamp > periodFinish ? periodFinish : (uint64)(block.timestamp); }
#0 - ColaM12
2022-02-02T19:07:07Z
Duplicate to #137
47.0985 USDT - $47.10
WatchPug
The library ReentrancyGuard
is imported and inherited, but the modifier nonReentrant
is unused.
contract LPoolDepositor is ReentrancyGuard {
Remove the import and change to:
contract LPoolDepositor {