Trader Joe contest - WatchPug's results

One-stop-shop decentralized trading on Avalanche.

General Information

Platform: Code4rena

Start Date: 25/01/2022

Pot Size: $50,000 USDT

Total HM: 17

Participants: 39

Period: 3 days

Judge: LSDan

Total Solo HM: 9

Id: 79

League: ETH

Trader Joe

Findings Distribution

Researcher Performance

Rank: 13/39

Findings: 6

Award: $1,506.17

🌟 Selected for report: 11

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: WatchPug

Also found by: p4st13r4

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

700.6157 USDT - $700.62

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/119e12d715ececc31478e833297f124cc15d27c2/contracts/RocketJoeFactory.sol#L97-L132

function createRJLaunchEvent(
    address _issuer,
    uint256 _phaseOneStartTime,
    address _token,
    uint256 _tokenAmount,
    uint256 _tokenIncentivesPercent,
    uint256 _floorPrice,
    uint256 _maxWithdrawPenalty,
    uint256 _fixedWithdrawPenalty,
    uint256 _maxAllocation,
    uint256 _userTimelock,
    uint256 _issuerTimelock
) external override returns (address) {
    require(
        getRJLaunchEvent[_token] == address(0),
        "RJFactory: token has already been issued"
    );
    require(_issuer != address(0), "RJFactory: issuer can't be 0 address");
    require(_token != address(0), "RJFactory: token can't be 0 address");
    require(_token != wavax, "RJFactory: token can't be wavax");
    require(
        _tokenAmount > 0,
        "RJFactory: token amount needs to be greater than 0"
    );
    require(
        IJoeFactory(factory).getPair(_token, wavax) == address(0) ||
            IJoePair(IJoeFactory(factory).getPair(_token, wavax))
                .totalSupply() ==
            0,
        "RJFactory: liquid pair already exists"
    );

    address launchEvent = Clones.clone(eventImplementation);

    // msg.sender needs to approve RocketJoeFactory
    IERC20(_token).transferFrom(msg.sender, launchEvent, _tokenAmount);

In the current implementation, RocketJoeFactory.sol#createRJLaunchEvent() can be called by anyone with at least 1 Wei of _token.

This allows a malicious user or attacker to call createRJLaunchEvent() with minimal cost and stop others, especially the platform itself or the rightful issuer of the token from creating the RJLaunchEvent.

Recommendation

Consider making createRJLaunchEvent() only callable by the owner of RocketJoeFactory.

#0 - cryptofish7

2022-02-10T14:16:15Z

That’s the spirit, not a single token should be in circulation

Findings Information

Labels

bug
duplicate
2 (Med Risk)

Awards

60.3184 USDT - $60.32

External Links

Handle

WatchPug

Vulnerability details

It is usually good to add a require-statement that checks the return value or to use something like safeTransfer; unless one is sure the given token reverts in case of a failure.

Instances include:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L490-L490

token.transfer(msg.sender, amount);

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L458-L458

token.transfer(msg.sender, amount);

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L514-L514

token.transfer(issuer, balance);

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L538-L538

token.transfer(penaltyCollector, excessToken);

Recommendation

Consider adding a require-statement or using safeTransfer.

#0 - cryptofish7

2022-01-31T14:46:25Z

Duplicate of #12

#1 - dmvt

2022-02-22T10:50:44Z

This could result in a loss of funds given the right external conditions.

2 β€” Med (M): vulns have a risk of 2 and are considered β€œMedium” severity when assets are not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements.

Findings Information

🌟 Selected for report: cmichel

Also found by: UncleGrandpa925, WatchPug, harleythedog

Labels

bug
duplicate
2 (Med Risk)
sponsor confirmed

Awards

283.7493 USDT - $283.75

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/119e12d715ececc31478e833297f124cc15d27c2/contracts/LaunchEvent.sol#L376-L388

function createPair() external isStopped(false) atPhase(Phase.PhaseThree) {
    (address wavaxAddress, address tokenAddress) = (
        address(WAVAX),
        address(token)
    );
    require(
        factory.getPair(wavaxAddress, tokenAddress) == address(0) ||
            IJoePair(
                IJoeFactory(factory).getPair(wavaxAddress, tokenAddress)
            ).totalSupply() ==
            0,
        "LaunchEvent: liquid pair already exists"
    );

createPair() is supposed to be called after phase 3 has started to finalize the launch event and all users can withdrawLiquidity() after this.

However, since createPair() requires the wavaxAddress, tokenAddress pair not existing or the totalSupply == 0, if someone (can be an attacker, a malicious user, or just a regular user) add liquidity to the pair during phase 1 and 2, or phase 3 before createPair() is called, it will make the launch event can not be finalized, therefore, forcing the event to be canceled and put into emergencyWithdraw mode.

Recommendation

Consider allowing createPair() when the pair is at a certain price range, so that even in the case above, the launch event can still be finalized, by combining a swap tx to correct the price to the target range with the createPair() tx.

#0 - cryptofish7

2022-02-01T00:40:54Z

#1 - dmvt

2022-02-22T19:49:59Z

duplicate of #197

Findings Information

🌟 Selected for report: cmichel

Also found by: Czar102, Ruhum, Tomio, WatchPug, defsec, hack3r-0m, hyh, saian

Labels

bug
duplicate
2 (Med Risk)
sponsor confirmed

Awards

74.4672 USDT - $74.47

External Links

Handle

WatchPug

Vulnerability details

It is usually good to add a require-statement that checks the return value or to use something like safeTransferFrom; unless one is sure the given token reverts in case of a failure.

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L80-L155

/// ...
/// @param _token Token that will be issued through this launch event
/// ...
function createRJLaunchEvent(
    // ...
    address _token,
    // ...
) external override returns (address) {
    // ...
    IERC20(_token).transferFrom(msg.sender, launchEvent, _tokenAmount);
    // ...
}

Recommendation

Consider adding a require-statement or using safeTransferFrom.

#0 - cryptofish7

2022-01-31T22:29:31Z

#1 - dmvt

2022-02-22T19:25:19Z

duplicate of #198

Findings Information

🌟 Selected for report: WatchPug

Also found by: pauliax

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

233.5386 USDT - $233.54

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/119e12d715ececc31478e833297f124cc15d27c2/contracts/RocketJoeFactory.sol#L97-L154

function createRJLaunchEvent(
    address _issuer,
    uint256 _phaseOneStartTime,
    address _token,
    uint256 _tokenAmount,
    uint256 _tokenIncentivesPercent,
    uint256 _floorPrice,
    uint256 _maxWithdrawPenalty,
    uint256 _fixedWithdrawPenalty,
    uint256 _maxAllocation,
    uint256 _userTimelock,
    uint256 _issuerTimelock
) external override returns (address) {
    require(
        getRJLaunchEvent[_token] == address(0),
        "RJFactory: token has already been issued"
    );
    require(_issuer != address(0), "RJFactory: issuer can't be 0 address");
    require(_token != address(0), "RJFactory: token can't be 0 address");
    require(_token != wavax, "RJFactory: token can't be wavax");
    require(
        _tokenAmount > 0,
        "RJFactory: token amount needs to be greater than 0"
    );
    require(
        IJoeFactory(factory).getPair(_token, wavax) == address(0) ||
            IJoePair(IJoeFactory(factory).getPair(_token, wavax))
                .totalSupply() ==
            0,
        "RJFactory: liquid pair already exists"
    );

    address launchEvent = Clones.clone(eventImplementation);

    // msg.sender needs to approve RocketJoeFactory
    IERC20(_token).transferFrom(msg.sender, launchEvent, _tokenAmount);

    ILaunchEvent(payable(launchEvent)).initialize(
        _issuer,
        _phaseOneStartTime,
        _token,
        _tokenIncentivesPercent,
        _floorPrice,
        _maxWithdrawPenalty,
        _fixedWithdrawPenalty,
        _maxAllocation,
        _userTimelock,
        _issuerTimelock
    );

    getRJLaunchEvent[_token] = launchEvent;
    isRJLaunchEvent[launchEvent] = true;
    allRJLaunchEvents.push(launchEvent);

    _emitLaunchedEvent(_issuer, _token, _phaseOneStartTime);

    return launchEvent;
}

At L132, _token.transferFrom() can be used to re-enter the createRJLaunchEvent() function, before the storage change at L147-149.

This will allow the attacker to create multiple launchEvent contracts and get them listed in allRJLaunchEvents.

Even though there is no significant impact as far as we can tell from the smart contract code. We believe this is still unexpected and may cause other parts of the system, say the frontend to malfunction in some cases.

Recommendation

Consider moving L132 _token.transferFrom() to after L147-149 to prevent re-entrance.

#0 - cryptofish7

2022-01-31T22:34:33Z

Findings Information

🌟 Selected for report: Dravee

Also found by: 0x1f8b, WatchPug, bobi, defsec, pauliax

Labels

bug
duplicate
1 (Low Risk)

Awards

51.0749 USDT - $51.07

External Links

Handle

WatchPug

Vulnerability details

There are many functions across the codebase that will perform an ERC20.approve() call but does not check the success return value. Some tokens do not revert if the approval failed but return false instead.

Instances include:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L407-L408

        WAVAX.approve(address(router), wavaxReserve);
        token.approve(address(router), tokenAllocated);

It is usually good to add a require-statement that checks the return value or to use something like SafeERC20#safeIncreaseAllowance(); unless one is sure the given token reverts in case of a failure.

#0 - cryptofish7

2022-01-31T14:30:51Z

Duplicate of #154

Findings Information

🌟 Selected for report: WatchPug

Also found by: 0x0x0x, Czar102, Dravee, Jujic, Meta0xNull, Ruhum, byterocket, defsec, gzeon, pedroais, robee, solgryn

Labels

bug
G (Gas Optimization)
sponsor confirmed

Awards

0.8642 USDT - $0.86

External Links

#0 - cryptofish7

2022-01-31T20:09:39Z

Findings Information

🌟 Selected for report: gzeon

Also found by: 0x1f8b, Ruhum, WatchPug, bobi, csanuragjain

Labels

bug
duplicate
G (Gas Optimization)

Awards

3.9149 USDT - $3.91

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L21-L21

address public override eventImplementation;

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L25-L25

address public override wavax;

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L45-L72

constructor(
    address _eventImplementation,
    address _rJoe,
    address _wavax,
    address _penaltyCollector,
    address _router,
    address _factory
) {
    // ...
    eventImplementation = _eventImplementation;
    // ...
    wavax = _wavax;
    // ...
}

In RocketJoeFactory.sol, eventImplementation and wavax will never change, use immutable variable instead of storage variable can save gas.

#0 - cryptofish7

2022-01-31T00:27:15Z

Duplicate of #284

Findings Information

🌟 Selected for report: WatchPug

Also found by: Czar102, Dravee, Jujic, Meta0xNull, byterocket, defsec, p4st13r4, pauliax, robee, sirhashalot

Labels

bug
G (Gas Optimization)
sponsor acknowledged

Awards

1.2609 USDT - $1.26

External Links

Handle

WatchPug

Vulnerability details

Every reason string takes at least 32 bytes.

Use short reason strings that fits in 32 bytes or it will become more expensive.

Instances include:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeStaking.sol#L65-L68

        require(
            _startTime > block.timestamp,
            "RocketJoeStaking: rJOE minting needs to start after the current timestamp"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeStaking.sol#L118-L121

        require(
            user.amount >= _amount,
            "RocketJoeStaking: withdraw amount exceeds balance"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeToken.sol#L17-L20

        require(
            rocketJoeFactory.isRJLaunchEvent(msg.sender),
            "RocketJoeToken: caller is not a RJLaunchEvent"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeToken.sol#L26-L29

        require(
            address(rocketJoeFactory) == address(0),
            "RocketJoeToken: already initialized"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L53-L61

        require(
            _eventImplementation != address(0) &&
                _rJoe != address(0) &&
                _wavax != address(0) &&
                _penaltyCollector != address(0) &&
                _router != address(0) &&
                _factory != address(0),
            "RJFactory: Addresses can't be null address"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L111-L128

        require(
            getRJLaunchEvent[_token] == address(0),
            "RJFactory: token has already been issued"
        );
        require(_issuer != address(0), "RJFactory: issuer can't be 0 address");
        require(_token != address(0), "RJFactory: token can't be 0 address");
        require(_token != wavax, "RJFactory: token can't be wavax");
        require(
            _tokenAmount > 0,
            "RJFactory: token amount needs to be greater than 0"
        );
        require(
            IJoeFactory(factory).getPair(_token, wavax) == address(0) ||
                IJoePair(IJoeFactory(factory).getPair(_token, wavax))
                    .totalSupply() ==
                0,
            "RJFactory: liquid pair already exists"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L206-L209

            require(
                _duration > PHASE_ONE_NO_FEE_DURATION,
                "RJFactory: phase one duration lower than no fee duration"
            );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L223-L226

        require(
            _noFeeDuration < PHASE_ONE_DURATION,
            "RJFactory: no fee duration bigger than phase one duration"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L237-L256

        require(
            _maxWithdrawPenalty <= 5e17,
            "LaunchEvent: maxWithdrawPenalty too big"
        ); // 50%
        require(
            _fixedWithdrawPenalty <= 5e17,
            "LaunchEvent: fixedWithdrawPenalty too big"
        ); // 50%
        require(
            _userTimelock <= 7 days,
            "LaunchEvent: can't lock user LP for more than 7 days"
        );
        require(
            _issuerTimelock > _userTimelock,
            "LaunchEvent: issuer can't withdraw before users"
        );
        require(
            _auctionStart > block.timestamp,
            "LaunchEvent: start of phase 1 cannot be in the past"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L312-L316

        require(msg.sender != issuer, "LaunchEvent: issuer cannot participate");
        require(
            msg.value > 0,
            "LaunchEvent: expected non-zero AVAX to deposit"
        );

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L497-L500

            require(
                user.balance > 0,
                "LaunchEvent: expected user to have non-zero balance to perform emergency withdraw"
            );

Findings Information

🌟 Selected for report: WatchPug

Also found by: Dravee, Jujic, Rhynorater, TomFrenchBlockchain, defsec, hyh, ye0lde

Labels

bug
G (Gas Optimization)
sponsor acknowledged

Awards

2.3783 USDT - $2.38

External Links

Handle

WatchPug

Vulnerability details

For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.

For example:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L327-L333

if (newAllocation > user.allocation) {
    // Burn tokens and update allocation.
    rJoeNeeded = getRJoeAmount(newAllocation - user.allocation);
    // ...
}

newAllocation - user.allocation will never underflow.

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeStaking.sol#L116-L135

    function withdraw(uint256 _amount) external {
        UserInfo storage user = userInfo[msg.sender];
        require(
            user.amount >= _amount,
            "RocketJoeStaking: withdraw amount exceeds balance"
        );

        updatePool();

        uint256 pending = (user.amount * accRJoePerShare) /
            PRECISION -
            user.rewardDebt;

        user.amount = user.amount - _amount;
        // ...
    }

user.amount - _amount will never underflow.

Findings Information

🌟 Selected for report: WatchPug

Also found by: Ruhum, TomFrenchBlockchain, WatchPug, byterocket, hyh, kirk-baird

Labels

bug
G (Gas Optimization)
sponsor confirmed

Awards

3.9149 USDT - $3.91

External Links

Handle

WatchPug

Vulnerability details

Every call to an external contract costs a decent amount of gas. For optimization of gas usage, external call results should be cached if they are being used for more than one time.

For example:

factory.getPair(wavaxAddress, tokenAddress) and factory.getPair(tokenAddress, wavaxAddress) in LaunchEvent#createPair()

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L377-L435

function createPair() external isStopped(false) atPhase(Phase.PhaseThree) {
    // ...
    require(
        factory.getPair(wavaxAddress, tokenAddress) == address(0) ||
            IJoePair(
                IJoeFactory(factory).getPair(wavaxAddress, tokenAddress)
            ).totalSupply() ==
            0,
        "LaunchEvent: liquid pair already exists"
    );
    // ...
    pair = IJoePair(factory.getPair(tokenAddress, wavaxAddress));
    // ...
}

note: factory.getPair(a, b) 与 factory.getPair(b, a) η›ΈεŒ, see code at github or code at avascan

getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction

IJoeFactory(factory).getPair(_token, wavax) in RocketJoeFactory#createRJLaunchEvent()

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L122-L128

require(
    IJoeFactory(factory).getPair(_token, wavax) == address(0) ||
        IJoePair(IJoeFactory(factory).getPair(_token, wavax))
            .totalSupply() ==
        0,
    "RJFactory: liquid pair already exists"
);

token.decimals() in LaunchEvent#createPair()

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L395-L405

if (
    floorPrice > (wavaxReserve * 10**token.decimals()) / tokenAllocated
) {
    tokenAllocated = (wavaxReserve * 10**token.decimals()) / floorPrice;
    // ...
}

#0 - cryptofish7

2022-01-31T22:55:29Z

Findings Information

🌟 Selected for report: WatchPug

Also found by: Ruhum, TomFrenchBlockchain, WatchPug, byterocket, hyh, kirk-baird

Labels

bug
duplicate
G (Gas Optimization)

Awards

3.9149 USDT - $3.91

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L377-L435

function createPair() external isStopped(false) atPhase(Phase.PhaseThree) {
    (address wavaxAddress, address tokenAddress) = (
        address(WAVAX),
        address(token)
    );
    // ...
    if (
        floorPrice > (wavaxReserve * 10**token.decimals()) / tokenAllocated
    ) {
        tokenAllocated = (wavaxReserve * 10**token.decimals()) / floorPrice;
        // ...
    }

    WAVAX.approve(address(router), wavaxReserve);
    token.approve(address(router), tokenAllocated);
    // ...
}
  • WAVAX at L407 is already cached in the local variable wavaxAddress at L378 ~ L379.
  • token at L396, L398, L408 is already cached in the local variable tokenAddress at L378 ~ L380.

Reusing the cached local variable instead of reading from storage again can save gas.

Read from the stack can save ~100 gas from each extra read (SLOAD after Berlin).

#0 - cryptofish7

2022-01-31T14:19:11Z

Duplicate of #236

Findings Information

🌟 Selected for report: WatchPug

Also found by: Ruhum, TomFrenchBlockchain, robee

Labels

bug
G (Gas Optimization)
sponsor acknowledged

Awards

7.2498 USDT - $7.25

External Links

Handle

WatchPug

Vulnerability details

For the storage variables that will be accessed multiple times, cache and read from the stack can save ~100 gas from each extra read (SLOAD after Berlin).

For example:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L327-L333

if (newAllocation > user.allocation) {
    // Burn tokens and update allocation.
    rJoeNeeded = getRJoeAmount(newAllocation - user.allocation);
    // ...
}

user.allocation can be cached.

Recommendation

Change to:

uint256 oldAllocation = user.allocation;
if (newAllocation > oldAllocation) {
    // Burn tokens and update allocation.
    rJoeNeeded = getRJoeAmount(newAllocation - oldAllocation);
    // ...
}

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L455-L459

if (tokenReserve > 0) {
    uint256 amount = tokenReserve;
    tokenReserve = 0;
    token.transfer(msg.sender, amount);
}

tokenReserve can be cached.

Recommendation

Change to:

uint256 amount = tokenReserve;
if (amount > 0) {
    tokenReserve = 0;
    token.transfer(msg.sender, amount);
}

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L290-L302

function currentPhase() public view returns (Phase) {
    if (block.timestamp < auctionStart || auctionStart == 0) {
        return Phase.NotStarted;
    } else if (block.timestamp < auctionStart + PHASE_ONE_DURATION) {
        return Phase.PhaseOne;
    } else if (
        block.timestamp <
        auctionStart + PHASE_ONE_DURATION + PHASE_TWO_DURATION
    ) {
        return Phase.PhaseTwo;
    }
    return Phase.PhaseThree;
}

auctionStart can be cached.

Recommendation

Change to:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L290-L303

function currentPhase() public view returns (Phase) {
    uint256 auctionStart_ = auctionStart;
    if (block.timestamp < auctionStart_ || auctionStart_ == 0) {
        return Phase.NotStarted;
    } else if (block.timestamp < auctionStart_ + PHASE_ONE_DURATION) {
        return Phase.PhaseOne;
    } else if (
        block.timestamp <
        auctionStart_ + PHASE_ONE_DURATION + PHASE_TWO_DURATION
    ) {
        return Phase.PhaseTwo;
    }
    return Phase.PhaseThree;
}

Findings Information

🌟 Selected for report: WatchPug

Labels

bug
G (Gas Optimization)
sponsor confirmed

Awards

39.7792 USDT - $39.78

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L98-L155

    function createRJLaunchEvent(
        address _issuer,
        uint256 _phaseOneStartTime,
        address _token,
        uint256 _tokenAmount,
        uint256 _tokenIncentivesPercent,
        uint256 _floorPrice,
        uint256 _maxWithdrawPenalty,
        uint256 _fixedWithdrawPenalty,
        uint256 _maxAllocation,
        uint256 _userTimelock,
        uint256 _issuerTimelock
    ) external override returns (address) {
        require(
            getRJLaunchEvent[_token] == address(0),
            "RJFactory: token has already been issued"
        );
        require(_issuer != address(0), "RJFactory: issuer can't be 0 address");
        require(_token != address(0), "RJFactory: token can't be 0 address");
        require(_token != wavax, "RJFactory: token can't be wavax");
        require(
            _tokenAmount > 0,
            "RJFactory: token amount needs to be greater than 0"
        );
        require(
            IJoeFactory(factory).getPair(_token, wavax) == address(0) ||
                IJoePair(IJoeFactory(factory).getPair(_token, wavax))
                    .totalSupply() ==
                0,
            "RJFactory: liquid pair already exists"
        );
        // ...
    }

_issuer != address(0), _token != address(0), _tokenAmount > 0 are cheaper than other checks who read storage or do external call.

Therefore, checking _issuer != address(0), _token != address(0), _tokenAmount > 0 first can save some gas.

Recommendation

Change to:

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeFactory.sol#L98-L155

    function createRJLaunchEvent(
        address _issuer,
        uint256 _phaseOneStartTime,
        address _token,
        uint256 _tokenAmount,
        uint256 _tokenIncentivesPercent,
        uint256 _floorPrice,
        uint256 _maxWithdrawPenalty,
        uint256 _fixedWithdrawPenalty,
        uint256 _maxAllocation,
        uint256 _userTimelock,
        uint256 _issuerTimelock
    ) external override returns (address) {
        require(_issuer != address(0), "RJFactory: issuer can't be 0 address");
        require(_token != address(0), "RJFactory: token can't be 0 address");
        require(
            _tokenAmount != 0,
            "RJFactory: token amount needs to be greater than 0"
        );
        require(
            getRJLaunchEvent[_token] == address(0),
            "RJFactory: token has already been issued"
        );
        require(_token != wavax, "RJFactory: token can't be wavax");
        require(
            IJoeFactory(factory).getPair(_token, wavax) == address(0) ||
                IJoePair(IJoeFactory(factory).getPair(_token, wavax))
                    .totalSupply() ==
                0,
            "RJFactory: liquid pair already exists"
        );
        // ...
    }

#0 - cryptofish7

2022-01-31T22:38:11Z

Findings Information

🌟 Selected for report: WatchPug

Also found by: 0x1f8b

Labels

bug
disagree with severity
G (Gas Optimization)
sponsor confirmed

Awards

17.9007 USDT - $17.90

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L82-L82

IJoeFactory private factory;

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L385-L385

IJoeFactory(factory).getPair(wavaxAddress, tokenAddress)

factory is defined as IJoeFactory already, the type casting is redundant.

#0 - cryptofish7

2022-01-31T23:17:50Z

#1 - dmvt

2022-02-22T19:33:08Z

I'm going to change this to a gas optimization since that's the only real benefit gained by making the change. I'm not sure that the change actually makes the code more clear, so non-critical doesn't really fit.

Findings Information

🌟 Selected for report: WatchPug

Also found by: Dravee, TomFrenchBlockchain, wuwe1

Labels

bug
disagree with severity
G (Gas Optimization)
sponsor confirmed

Awards

7.2498 USDT - $7.25

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/LaunchEvent.sol#L19-L19

contract LaunchEvent is Ownable {

The LaunchEvent.sol contract never utilized onlyOwner / owner() or any other features provided by the Ownable library.

Therefore, is Ownable can be removed.

#0 - cryptofish7

2022-01-31T23:01:39Z

#1 - dmvt

2022-02-22T19:47:02Z

Leaving the unnecessary contract out will save gas depending on how you deploy.

Findings Information

🌟 Selected for report: WatchPug

Also found by: jayjonah8

Labels

bug
G (Gas Optimization)

Awards

17.9007 USDT - $17.90

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2022-01-trader-joe/blob/a1579f6453bc4bf9fb0db9c627beaa41135438ed/contracts/RocketJoeStaking.sol#L116-L135

function withdraw(uint256 _amount) external {
    UserInfo storage user = userInfo[msg.sender];
    require(
        user.amount >= _amount,
        "RocketJoeStaking: withdraw amount exceeds balance"
    );

    updatePool();

    uint256 pending = (user.amount * accRJoePerShare) /
        PRECISION -
        user.rewardDebt;

    user.amount = user.amount - _amount;
    user.rewardDebt = (user.amount * accRJoePerShare) / PRECISION;

    _safeRJoeTransfer(msg.sender, pending);
    joe.safeTransfer(address(msg.sender), _amount);
    emit Withdraw(msg.sender, _amount);
}

Since _amount can be 0. Checking if (_amount != 0) before the transfer can potentially save an external call and the unnecessary gas cost of a 0 token transfer.

When withdraw(0), user.amount = user.amount - _amount; can be skipped, to save storage write and external call of joe.safeTransfer(address(msg.sender), _amount);.

#0 - cryptofish7

2022-01-30T23:56:22Z

Duplicate of #317

#1 - dmvt

2022-02-23T12:06:38Z

This does point to a valid gas optimization. Rewards could be disbursed without sending a corresponding zero transfer. Using an if statement here would have a net positive impact on gas. Even better would be to split into the more common withdraw / withdrawAndClaim pattern.

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