Wenwin contest - 0x1f8b's results

The next generation of chance-based gaming.

General Information

Platform: Code4rena

Start Date: 06/03/2023

Pot Size: $36,500 USDC

Total HM: 8

Participants: 93

Period: 3 days

Judge: cccz

Total Solo HM: 3

Id: 218

League: ETH

Wenwin

Findings Distribution

Researcher Performance

Rank: 47/93

Findings: 2

Award: $34.42

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

21.7018 USDC - $21.70

Labels

bug
grade-b
QA (Quality Assurance)
sponsor disputed
Q-18

External Links

Low

1. Integer overflow by unsafe casting

Keep in mind that the version of solidity used, despite being greater than 0.8, does not prevent integer overflows during casting, it only does so in mathematical operations.

It is necessary to safely convert between the different numeric types.

Recommendation:

Use a safeCast from Open Zeppelin.

    function getPercentageInt(int256 number, uint256 percentage) internal pure returns (int256 result) {
        return number * int256(percentage) / int256(PERCENTAGE_BASE);
    }

Affected source code:

2. Lack of checks address(0)

The following methods have a lack of checks if the received argument is an address, it's good practice in order to reduce human error to check that the address specified in the constructor or initialize is different than address(0).

Affected source code:

3. Lack of checks supportsInterface

The EIP-165 standard helps detect that a smart contract implements the expected logic, prevents human error when configuring smart contract bindings, so it is recommended to check that the received argument is a contract and supports the expected interface.

Reference:

Affected source code:

4. Lack of checks integer ranges

The following methods lack checks on the following integer arguments, you can see the recommendations above.

Affected source code:

For example, funds deposited in StakedTokenLock could be locked forever with a bad initial configuration.

5. Incompatible with tokens without decimals

Cannot set a rewardToken without decimals. Because the pragma used doesn't allow integer underflows, if the reward token used has 0 decimals, an integer underflow will produce a denial of service.

Affected source code:

6. The referer should never be yourself

The referrerTicketCount counter of the same player can be increased, in order to increase its rewards in claimPerDraw, for this the msg.sender must be established as referrer.

Affected source code:

7. Centralized risk

Nothing ensures that rewards tokens are available to give to the user, so if they are not transferred to the contract, the getReward method will be denied.

Affected source code:


Non critical

8. Unify return criteria

In TicketUtils.sol and LotterySetup.sol files, the methods are sometimes returned with return, other times the return variable is equal, and on other occasions the name of the return variable is not defined, It is recommended to unify the way of writing the code to make it more uniform and readable.

In the same file you can see different behaviors when programming returns. This behavior is observed across multiple contracts.

Affected source code:

9. Improve naming

Authoritative internals methods must use '_' before the name. We have an example in Ticket.sol:19 where the methods that require authorization and it's internal, a different naming should be used.

Affected source code:

#0 - thereksfour

2023-03-12T09:39:15Z

1 L 3 INFO 4 NC

#1 - c4-judge

2023-03-12T09:39:26Z

thereksfour marked the issue as grade-b

#2 - c4-sponsor

2023-03-14T10:23:25Z

0xluckydev marked the issue as sponsor disputed

#3 - thereksfour

2023-03-17T13:38:02Z

1L 3 INFO B

Awards

12.7206 USDC - $12.72

Labels

bug
G (Gas Optimization)
grade-b
sponsor acknowledged
G-07

External Links

Gas

1. Reorder structure layout

The following structures could be optimized moving the position of certain values in order to save some storage slots:

struct LotterySetupParams {
+   /// @dev Count of numbers user picks for the ticket
+   uint8 selectionSize;
+   /// @dev Max number user can pick
+   uint8 selectionMax;
    /// @dev Token to be used as reward token for the lottery
    IERC20 token;
    /// @dev Parameters of the draw schedule for the lottery
    LotteryDrawSchedule drawSchedule;
    /// @dev Price to pay for playing single game (including fee)
    uint256 ticketPrice;
-   /// @dev Count of numbers user picks for the ticket
-   uint8 selectionSize;
-   /// @dev Max number user can pick
-   uint8 selectionMax;
    /// @dev Expected payout for one ticket, expressed in `rewardToken`
    uint256 expectedPayout;
    /// @dev Array of fixed rewards per each non jackpot win
    uint256[] fixedRewards;
}

Reference:

Enums are represented by integers; the possibility listed first by 0, the next by 1, and so forth. An enum type just acts like uintN, where N is the smallest legal value large enough to accomodate all the possibilities.

Affected source code:

2. Optimize conditional

-       bool notEnoughRetryInvocations = failedSequentialAttempts < maxFailedAttempts;
-       bool notEnoughTimeReachingMaxFailedAttempts = block.timestamp < maxFailedAttemptsReachedAt + maxRequestDelay;
-       if (notEnoughRetryInvocations || notEnoughTimeReachingMaxFailedAttempts) {
+       if ((failedSequentialAttempts < maxFailedAttempts)) || (block.timestamp < maxFailedAttemptsReachedAt + maxRequestDelay)) {
            revert NotEnoughFailedAttempts();
        }

There is an OR conditional that previously computes both conditions. The advantage of an OR conditional is that if one condition is met, the code of the rest of the conditional will not be executed, and therefore gas will be saved, so by modifying the code as shown previously, an optimization will be made when reverting said method.

Affected source code:

Some gas differences:

- [PASS] testSuccessfulSwapSource() (gas: 101837)
+ [PASS] testSuccessfulSwapSource() (gas: 101816)
- [PASS] testSwapSourceFailsWithInsufficientFailedAttempts() (gas: 85632)
+ [PASS] testSwapSourceFailsWithInsufficientFailedAttempts() (gas: 83427)
- [PASS] testSwapSourceFailsWithInsufficientFailedAttemptsWhenNotEnoughTimeSinceReachingMaxFailedAttempts() (gas: 114248)
+ [PASS] testSwapSourceFailsWithInsufficientFailedAttemptsWhenNotEnoughTimeSinceReachingMaxFailedAttempts() (gas: 114226)

3. Use require instead of assert

The assert() and require() functions are a part of the error handling aspect in Solidity. Solidity makes use of state-reverting error handling exceptions. This means all changes made to the contract on that call or any sub-calls are undone if an error is thrown. It also flags an error.

They are quite similar as both check for conditions and if they are not met, would throw an error.

The big difference between the two is that the assert() function when false, uses up all the remaining gas and reverts all the changes made.

Meanwhile, a require() function when false, also reverts back all the changes made to the contract but does refund all the remaining gas fees we offered to pay. This is the most common Solidity function used by developers for debugging and error handling.

Affected source code:

Some gas differences:

- [PASS] testFinalizeInitialPot(uint256,uint256) (runs: 256, μ: 8541365, ~: 8536222)
+ [PASS] testFinalizeInitialPot(uint256,uint256) (runs: 256, μ: 8540713, ~: 8535622)

4. Use local variable instead of storage

Since the same value is previously stored in a local variable, it is cheaper to query its contents than to access the storage for such a query, so the following modification is recommended:

    function finalizeInitialPotRaise() external override {
        if (initialPot > 0) {
            revert JackpotAlreadyInitialized();
        }
        // slither-disable-next-line timestamp
        if (block.timestamp <= initialPotDeadline) {
            revert FinalizingInitialPotBeforeDeadline();
        }
        uint256 raised = rewardToken.balanceOf(address(this));
        if (raised < minInitialPot) {
            revert RaisedInsufficientFunds(raised);
        }
        initialPot = raised;

        // must hold after this call, this will be used as a check that jackpot is initialized
-       assert(initialPot > 0);
+       assert(raised > 0);

        emit InitialPotPeriodFinalized(raised);
    }

Affected source code:

Some gas differences:

- [PASS] testFinalizeInitialPot(uint256,uint256) (runs: 256, μ: 8541365, ~: 8536222)
+ [PASS] testFinalizeInitialPot(uint256,uint256) (runs: 256, μ: 8541168, ~: 8536222)

#0 - c4-judge

2023-03-12T14:07:06Z

thereksfour marked the issue as grade-b

#1 - c4-sponsor

2023-03-14T12:57:55Z

TutaRicky marked the issue as sponsor acknowledged

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