OpenLeverage contest - WatchPug's results

Permissionless lending and margin trading protocol that enables traders to long/short any trading pair on DEXs efficiently and securely.

General Information

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

OpenLeverage

Findings Distribution

Researcher Performance

Rank: 4/29

Findings: 3

Award: $5,395.56

🌟 Selected for report: 2

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
2 (Med Risk)
resolved
sponsor confirmed

Awards

4882.8125 USDT - $4,882.81

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-openleverage/blob/501e8f5c7ebaf1242572712626a77a3d65bdd3ad/openleverage-contracts/contracts/dex/bsc/UniV2ClassDex.sol#L31-L56

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.

https://github.com/code-423n4/2022-01-openleverage/blob/501e8f5c7ebaf1242572712626a77a3d65bdd3ad/openleverage-contracts/contracts/dex/bsc/UniV2ClassDex.sol#L101-L102

Recommendation

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.

Findings Information

🌟 Selected for report: samruna

Also found by: WatchPug, defsec

Labels

bug
duplicate
1 (Low Risk)

Awards

439.4531 USDT - $439.45

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-openleverage/blob/501e8f5c7ebaf1242572712626a77a3d65bdd3ad/openleverage-contracts/contracts/OLETokenLock.sol#L28-L37

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]);
    }
}

https://github.com/code-423n4/2022-01-openleverage/blob/501e8f5c7ebaf1242572712626a77a3d65bdd3ad/openleverage-contracts/contracts/OLETokenLock.sol#L72-L79

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.

Recommendation

Consider adding checks for input validation of releaseVar.endTime > releaseVar.startTime.

#0 - ColaM12

2022-02-02T19:30:52Z

Duplicate to #160

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter