veToken Finance contest - 0x1f8b's results

Lock more veAsset permanently.

General Information

Platform: Code4rena

Start Date: 26/05/2022

Pot Size: $75,000 USDT

Total HM: 31

Participants: 71

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 18

Id: 126

League: ETH

veToken Finance

Findings Distribution

Researcher Performance

Rank: 21/71

Findings: 3

Award: $967.85

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: xiaoming90

Also found by: 0x1f8b, VAD37

Labels

bug
duplicate
2 (Med Risk)
upgraded by judge

Awards

562.3122 USDT - $562.31

External Links

Judge has assessed an item in Issue #9 as Medium risk. The relevant finding follows: Centralized risk The operator address can mint arbitrary amount of tokens. In addition, operator can also burn tokens from third-party accounts.

If the private key of the owner or minter address is compromised, the attacker will be able to mint an unlimited amount of tokens, or burn from arbitrary addresses.

operator can mint and burn arbitrary tokens. I believe this is unnecessary and poses a serious centralization risk.

Recomendation:

Consider reduce centralization risks with timelock contracts. Affected source code:

VE3Token.sol#L26-L36 VeToken.sol#L23-L33 DepositToken.sol#L26-L36

#0 - JeeberC4

2022-07-28T19:50:56Z

Duplicate of #202

Low

Avoid duplicate entries

When BaseRewardPool.addExtraReward is called, it is not checked that the address already exists in the extraRewards array. This would allow the token to be duplicated and affect the economics of the project.

Affected source code:

Use hardcoded values

It is not good practice to hardcode values, but if you are dealing with addresses much less, these can change between implementations, networks or projects, so it is convenient to remove these addresses from the source code.

Affected source code:

Outdated compiler

The pragma version used are:

pragma solidity 0.8.7; pragma solidity >=0.4.0; pragma solidity >=0.4.22 <0.9.0; pragma solidity >=0.5.0;

But recently solidity released a new version with important Bugfixes:

  • The first one is related to ABI-encoding nested arrays directly from calldata. You can find more information here.

  • The second bug is triggered in certain inheritance structures and can cause a memory pointer to be interpreted as a calldata pointer or vice-versa. We also have a dedicated blog post about this bug.

Apart from these, there are several minor bug fixes and improvements.

The minimum required version should be 0.8.14

Lack of input checks

The following methods have a lack 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:

address(0):

Less than FEE_DENOMINATOR:

Lack of ACK during owner change

It's possible to lose the ownership under specific circumstances.

Because an human error it's possible to set a new invalid owner. When you want to change the owner's address it's better to propose a new owner, and then accept this ownership with the new wallet.

Affected source code:

owner:

feeManager:

setPoolManager:

Centralized risk

The operator address can mint arbitrary amount of tokens. In addition, operator can also burn tokens from third-party accounts.

If the private key of the owner or minter address is compromised, the attacker will be able to mint an unlimited amount of tokens, or burn from arbitrary addresses.

operator can mint and burn arbitrary tokens. I believe this is unnecessary and poses a serious centralization risk.

Recomendation:

  • Consider reduce centralization risks with timelock contracts.

Affected source code:

Owner can't be changed

Although the contract has an owner, this cannot be changed, so if you want to delegate or sell the project, it could not be done while keeping the same contracts.

Recomendation:

  • Consider adding the ability to change these governors.

Affected source code:

operator can't be changed:

dao can't be changed:

Non-Critical

Avoid errors with transferFrom

The following methods take all the user's balance and send it through transferFrom, this call may fail, since transferFrom extracts the balance from the previously approved allowance, it is better to use the user's allowance in order to avoid the unnecessary failure when both amounts are not the same. It's better to use allowance instead of balanceOf.

Affected source code:

#0 - jetbrain10

2022-06-15T21:07:28Z

high quality.

#1 - GalloDaSballo

2022-07-06T23:21:05Z

Avoid duplicate entries

Valid Low

Use hardcoded values

NC

Outdated compiler

Not valid as the compiler is set in truffle-config

Lack of input checks

Valid Low

Lack of ACK during owner change

NC

Centralized risk

Valid Low, may raise based on other submissions

Owner can't be changed

Disagree, this gives stronger security guarantees

Avoid errors with transferFrom

Disagree, it's up to the caller to ensure they have the proper allowance

Overall a pretty good short and sweet report

3L, 2 NC

#2 - GalloDaSballo

2022-07-28T17:08:29Z

2L, 2NC After bumping Centralized Risk

#3 - GalloDaSballo

2022-07-28T17:09:21Z

TODO: Bump Centralized Risk to Med, dup of M-10

Dead code

The following contracts are never used. It's an excellent idea to take them out to save gas and improve the codding style. The contract ForceSend is not used and it's very dangerous.

Affected source code:

Use constants

Use constants for static computations 2**255:

Use constant instead of storage for totalCliffs = 1000;:

Cache static keccack256 bytes4(keccak256("set_rewards_receiver(address)")):

++i costs less gas compared to i++ or i += 1

++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.

i++ increments i and returns the initial value of i. Which means:

uint i = 1;
i++; // == 1 but i == 2

But ++i returns the actual incremented value:

uint i = 1;
++i; // == 2 and i == 2 too, so no need for a temporary variable

In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2 I suggest using ++i instead of i++ to increment the value of an uint variable. Same thing for --i and i--

Affected source code:

Reduce boolean comparison

It's compared a boolean value using == true or == false, instead of using the boolean value. NOT opcode, it's cheaper to use EQUAL or NOTEQUAL when the value it's false, or just the value without == true when it's true, because it will use less opcodes inside the VM.

Remove == true in:

Change == false by ! in:

Reduce the size of error messages (Long revert Strings)

Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.

Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.

I suggest shortening the revert strings to fit in 32 bytes, or that using custom errors as described next (require pragma upgrade).

Use Custom Errors instead of Revert Strings to save Gas

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)

Source Custom Errors in Solidity:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).

Affected source code:

Unused imports

There are certain contract imports that are not used and can be removed.

using SafeERC20 for IERC20; using Address for address; using SafeMath for uint256;

Affected source code:

Gas saving using immutable.

It's possible to avoid storage access a save gas using immutable keyword for the following variables:

Affected source code:

Changing to immutable the variables _name and _symbol, the methods name and symbol could be pure:

Delete optimization

Use delete instead of set to default value (false or 0)

Affected source code:

Avoid unused returns

Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert in case of failure, it is not necessary to return a true, since it will never be false. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.

Affected source code:

Use storage pointers or memory

Use storage keyword for save gas in order to cache a storage pointer.

The recurring use of the same register reading from storage is a task that can be easily optimized by using storage or memory, a clear example is access to rewardTokenInfo[_rewardToken] but it can be seen throughout all contracts.

Affected source code:

rewardTokenInfo[_rewardToken]:

rewardData[_rewardsToken]:

#0 - GalloDaSballo

2022-07-14T01:41:42Z

Dead code

Test file, will ignore

Use constants

Use constants for static computations 2**255: -> Valid, saves 10 gas (EXP) Use constant instead of storage for totalCliffs = 1000;: -> Saves 2.1k (saves more but it's on the warden to demonstrate) (SLOAD) Cache static keccack256 bytes4(keccak256("set_rewards_receiver(address)")): -> Saves 30 gas (SHA)

2140

++i costs less gas compared to i++ or i += 1

5 per instance 5 * 34 = 170

Reduce boolean comparison

3 gas per instance 3 * 9 = 27

Reduce the size of error messages (Long revert Strings) + CustomErrors

Cannot quantify in lack of POC

Unused imports

Don't believe it will save gas as the compiler get's rid of them

Gas saving using immutable.

Huge gas savings, in lack of further details will only give it one SLOAD per instance 3+ 5 + 6 + 5 + 5 + 4 + 5 + 3 + 1 + 1 + 2 40 * 2100 = 84000

Last 3 findings are missing POC so I can't quantify

#1 - GalloDaSballo

2022-07-14T01:42:22Z

86337 gas saved

#2 - GalloDaSballo

2022-07-28T20:31:14Z

TODO: Remove Stash Findings as contracts are out of scope

#3 - GalloDaSballo

2022-07-28T22:39:51Z

Removing gas from Out of Scope Contracts

  • ExtraRewardsStashV1, V2 and V3
  • VirtualBalanceRewardPool
  • SmartWalletWhitelist
  • DepositToken
  • StashFactory

++i costs less gas compared to i++ or i += 1

5 * 8 -40

Immutables

22 * 2100

-46200

Gas Saved in Scope: 40137

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