Yieldy contest - IllIllI's results

A protocol for gaining single side yields on various tokens.

General Information

Platform: Code4rena

Start Date: 21/06/2022

Pot Size: $50,000 USDC

Total HM: 31

Participants: 99

Period: 5 days

Judges: moose-code, JasoonS, denhampreen

Total Solo HM: 17

Id: 139

League: ETH

Yieldy

Findings Distribution

Researcher Performance

Rank: 7/99

Findings: 4

Award: $2,433.19

🌟 Selected for report: 2

πŸš€ Solo Findings: 1

Findings Information

🌟 Selected for report: unforgiven

Also found by: IllIllI, TrungOre, asutorufos, hake, robee

Labels

bug
duplicate
2 (Med Risk)
sponsor acknowledged

Awards

545.2654 USDC - $545.27

External Links

Lines of code

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L188-L209

Vulnerability details

Impact

One of the tokens supported by this project is USDC, which is an upgradeable contract, and the code specifically casts addresses to IERC20Upgradeable rather than to IERC20, so the intention is for the code to support upgrades. If USDC ever upgrades to have a fee-on-transfer, rebasing behavior, or some other non-standard behavior, the contracts in this project will break, locking user funds in the state that they were before the upgrade.

Proof of Concept

For example, if an upgrade starts fee-on-transfer behavior, this function will revert, because it assumes _amount is available once claimeWithdraw() has happened, which will not be the case if a fee is charged:

File: src/contracts/LiquidityReserve.sol   #X

188       function instantUnstake(uint256 _amount, address _recipient)
189           external
190           onlyStakingContract
191       {
192           require(isReserveEnabled, "Not enabled yet");
193           // claim the stakingToken from previous unstakes
194           IStaking(stakingContract).claimWithdraw(address(this));
195   
196           uint256 amountMinusFee = _amount - ((_amount * fee) / BASIS_POINTS);
197   
198           IERC20Upgradeable(rewardToken).safeTransferFrom(
199               msg.sender,
200               address(this),
201               _amount
202           );
203   
204           IERC20Upgradeable(stakingToken).safeTransfer(
205               _recipient,
206               amountMinusFee
207           );
208           unstakeAllRewardTokens();
209       }

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L188-L209

There are a lot of other examples where the balances aren't checked

Tools Used

Code inspection

Measure balances before and after transfers, and use the difference as the amount rather than the stated amount.

#0 - toshiSat

2022-07-27T22:36:06Z

We will update the documentation to include these contracts will not work for ERC20 tokens that have fees

#1 - KenzoAgada

2022-08-26T09:02:58Z

In the judging sheet this was judged as unique, but looks like a duplicate of M-17 #222

Findings Information

🌟 Selected for report: IllIllI

Labels

bug
2 (Med Risk)
disagree with severity
sponsor disputed

Awards

1211.7009 USDC - $1,211.70

External Links

Lines of code

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L214-L225

Vulnerability details

Impact

Users may be unable to withdraw/remove their liquidity from the LiquidityReserve if a user decides to grief the contract.

Proof of Concept

This is the only function in this contract that is able to unstake funds, so that they can be withdrawn/removed:

File: src/contracts/LiquidityReserve.sol   #1

214       function unstakeAllRewardTokens() public {
215           require(isReserveEnabled, "Not enabled yet");
216           uint256 coolDownAmount = IStaking(stakingContract)
217               .coolDownInfo(address(this))
218               .amount;
219           if (coolDownAmount == 0) {
220               uint256 amount = IERC20Upgradeable(rewardToken).balanceOf(
221                   address(this)
222               );
223               if (amount > 0) IStaking(stakingContract).unstake(amount, false);
224           }
225       }

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L214-L225

The function requires that the coolDownAmount is zero, or else it skips the unstake() call. A malicious user can make coolDownAmount non-zero by calling Staking.instantUnstakeReserve() when the previous reward is claimed, with just a large enough amount to satisfy the transfer of the amount and of the fee, so there is essentially zero left for other users to withdraw. The function calls LiquidityReserve.instantUnstake():

File: src/contracts/Staking.sol   #2

571       function instantUnstakeReserve(uint256 _amount) external {
572           require(_amount > 0, "Invalid amount");
573           // prevent unstaking if override due to vulnerabilities
574           require(
575               !isUnstakingPaused && !isInstantUnstakingPaused,
576               "Unstaking is paused"
577           );
578   
579           rebase();
580           _retrieveBalanceFromUser(_amount, msg.sender);
581   
582           uint256 reserveBalance = IERC20Upgradeable(STAKING_TOKEN).balanceOf(
583               LIQUIDITY_RESERVE
584           );
585   
586           require(reserveBalance >= _amount, "Not enough funds in reserve");
587   
588           ILiquidityReserve(LIQUIDITY_RESERVE).instantUnstake(
589               _amount,
590               msg.sender
591           );
592       }

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L571-L592

Which boosts the cooldown amount above zero in its call to unstakeAllRewardTokens() and then IStaking.unstake():

File: src/contracts/LiquidityReserve.sol   #3

188       function instantUnstake(uint256 _amount, address _recipient)
189           external
190           onlyStakingContract
191       {
192           require(isReserveEnabled, "Not enabled yet");
193           // claim the stakingToken from previous unstakes
194           IStaking(stakingContract).claimWithdraw(address(this));
195   
196           uint256 amountMinusFee = _amount - ((_amount * fee) / BASIS_POINTS);
197   
198           IERC20Upgradeable(rewardToken).safeTransferFrom(
199               msg.sender,
200               address(this),
201               _amount
202           );
203   
204           IERC20Upgradeable(stakingToken).safeTransfer(
205               _recipient,
206               amountMinusFee
207           );
208           unstakeAllRewardTokens();
209       }
210   
211       /**
212           @notice find balance of reward tokens in contract and unstake them from staking contract
213        */
214       function unstakeAllRewardTokens() public {
215           require(isReserveEnabled, "Not enabled yet");
216           uint256 coolDownAmount = IStaking(stakingContract)
217               .coolDownInfo(address(this))
218               .amount;
219           if (coolDownAmount == 0) {
220               uint256 amount = IERC20Upgradeable(rewardToken).balanceOf(
221                   address(this)
222               );
223               if (amount > 0) IStaking(stakingContract).unstake(amount, false);
224           }
225       }

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L188-L225

File: src/contracts/Staking.sol   #4

674       function unstake(uint256 _amount, bool _trigger) external {
675           // prevent unstaking if override due to vulnerabilities asdf
676           require(!isUnstakingPaused, "Unstaking is paused");
677           if (_trigger) {
678               rebase();
679           }
680           _retrieveBalanceFromUser(_amount, msg.sender);
681   
682           Claim storage userCoolInfo = coolDownInfo[msg.sender];
683   
684           // try to claim withdraw if user has withdraws to claim function will check if withdraw is valid
685           claimWithdraw(msg.sender);
686   
687           coolDownInfo[msg.sender] = Claim({
688               amount: userCoolInfo.amount + _amount,
689               credits: userCoolInfo.credits +
690                   IYieldy(YIELDY_TOKEN).creditsForTokenBalance(_amount),
691               expiry: epoch.number + coolDownPeriod
692           });

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L674-L692

If the malicious user is a miner, that miner can make sure that the block where the previous cooldown expires and is claimed, is the same block where the miner griefs by doing an instant unstake of a small amount, preventing larger amounts from going through. Until the miner decides to stop this behavior, funds will be locked in the contract.

Tools Used

Code inspection

Keep track of submitted amounts during the cooldown, and batch-submit them during the next open window, rather than making it first-come-first-served

#0 - 0xean

2022-06-27T21:52:09Z

The warden does identify a potential attack, but the assumptions that are being made for it to work are pretty hard to imagine. For one, It would require a miner to always be able to process a specific block in order to continually DOS the contract. This is very infeasible.

Additionally, if this scenario was to occur, we could pause instant unstaking and wait for the cooldown to expire in order to retrieve funds. The ability to toggle the instant unstake negates the call path the warden has suggested since Staking.instantUnstakeReserve() would revert.

Given all of this, I would put this entire attack vector as super low risk and suggest its downgraded to QA.

#1 - toshiSat

2022-06-27T21:53:38Z

sponsor dispute severity: QA

#2 - JasoonS

2022-07-30T15:24:55Z

I'm going to make this a Medium. While I agree the assumptions are out of the imaginable in reality, it is something that should be looked into for the contracts more seriously than the typical QA

Summary

Low Risk Issues

IssueInstances
1Batch-related functions will revert if removeAddress() is called2
2Staking contract's token not verified to be the same token as the staking token1
3Missing infinite approval functionality1
4Missing checks that the end time matches the duration1
5Missing input validations and timelocks5
6Front-runable initializer2

Total: 12 instances over 6 issues

Non-critical Issues

IssueInstances
1Return values of approve() not checked2
2Misleading variable names1
3public functions not called by the contract should be declared external instead1
4constants should be defined rather than using magic numbers3
5Use a more recent version of solidity3
6Typos10
7NatSpec is incomplete2
8Event is missing indexed fields12

Total: 34 instances over 8 issues

Low Risk Issues

1. Batch-related functions will revert if removeAddress() is called

removeAddress() removes entries from the storage array that holds batch contract addresses, but it doesn't fill in the deleted entries with a replacement value. When other functions hit these null entries and try to call functions on the zero address, they'll revert, causing the whole function to fail

There are 2 instances of this issue:

File: src/contracts/BatchRequests.sol   #1

33       function canBatchContracts() external view returns (Batch[] memory) {
34           uint256 contractsLength = contracts.length;
35           Batch[] memory batch = new Batch[](contractsLength);
36           for (uint256 i; i < contractsLength; ) {
37:              bool canBatch = IStaking(contracts[i]).canBatchTransactions();

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L33-L37

File: src/contracts/BatchRequests.sol   #2

50       function canBatchContractByIndex(uint256 _index)
51           external
52           view
53           returns (address, bool)
54       {
55           return (
56               contracts[_index],
57:              IStaking(contracts[_index]).canBatchTransactions()

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L50-L57

2. Staking contract's token not verified to be the same token as the staking token

There may be a mismatch between the token that _stakingContract is in charge of, and the actual token used by the LiquidityReserve. This code should check that they are in fact the same

There is 1 instance of this issue:

File: src/contracts/LiquidityReserve.sol   #1

57       function enableLiquidityReserve(address _stakingContract)
58           external
59           onlyOwner
60       {
61           require(!isReserveEnabled, "Already enabled");
62           require(_stakingContract != address(0), "Invalid address");
63   
64           uint256 stakingTokenBalance = IERC20Upgradeable(stakingToken).balanceOf(
65               msg.sender
66           );
67           // require address has minimum liquidity
68           require(
69               stakingTokenBalance >= MINIMUM_LIQUIDITY,
70               "Not enough staking tokens"
71           );
72:          stakingContract = _stakingContract;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L57-L72

3. Missing infinite approval functionality

Most other contracts in the repository use type(uint256).max to mean infinite approval, rather than a specific approval amount. Not doing the same thing here will mean inconsistent behavior between the components, will mean that approvals will eventually run down to zero, and will mean that there will be hard-to-track-down issues when things eventually start failing

There is 1 instance of this issue:

File: src/contracts/Yieldy.sol   #1

210          require(_allowances[_from][msg.sender] >= _value, "Allowance too low");
211  
212          uint256 newValue = _allowances[_from][msg.sender] - _value;
213          _allowances[_from][msg.sender] = newValue;
214:         emit Approval(_from, msg.sender, newValue);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L210-L214

4. Missing checks that the end time matches the duration

There is 1 instance of this issue:

File: src/contracts/Staking.sol   #1

95               duration: _epochDuration,
96               number: 1,
97               timestamp: block.timestamp, // we know about the issues surrounding block.timestamp, using it here will not cause any problems
98:              endTime: _firstEpochEndTime,

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L95-L98

5. Missing input validations and timelocks

The following instances are missing checks for zero addresses and or valid ranges for values. Even if the DAO is the one setting these values, it's important to add sanity checks in case someone does a fat-finger operation that is missed by DAO participants who may not be very technical. There are also no timelocks involved, which should be rectified

There are 5 instances of this issue:

File: src/contracts/Staking.sol

217      function setEpochDuration(uint256 duration) external onlyOwner {
218          epoch.duration = duration;
219          emit LogSetEpochDuration(block.number, duration);
220:     }

167      function setAffiliateFee(uint256 _affiliateFee) external onlyOwner {
168          affiliateFee = _affiliateFee;
169          emit LogSetAffiliateFee(block.number, _affiliateFee);
170:     }

217      function setEpochDuration(uint256 duration) external onlyOwner {
218          epoch.duration = duration;
219          emit LogSetEpochDuration(block.number, duration);
220:     }

226      function setWarmUpPeriod(uint256 _vestingPeriod) external onlyOwner {
227          warmUpPeriod = _vestingPeriod;
228          emit LogSetWarmUpPeriod(block.number, _vestingPeriod);
229:     }

235      function setCoolDownPeriod(uint256 _vestingPeriod) external onlyOwner {
236          coolDownPeriod = _vestingPeriod;
237          emit LogSetCoolDownPeriod(block.number, _vestingPeriod);
238:     }

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L217-L220

6. Front-runable initializer

There is nothing preventing another account from calling the initializer before the contract owner. In the best case, the owner is forced to waste gas and re-deploy. In the worst case, the owner does not notice that his/her call reverts, and everyone starts using a contract under the control of an attacker

There are 2 instances of this issue:

File: src/contracts/LiquidityReserve.sol   #1

36       function initialize(
37           string memory _tokenName,
38           string memory _tokenSymbol,
39           address _stakingToken,
40           address _rewardToken
41:      ) external initializer {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L36-L41

File: src/contracts/Yieldy.sol   #2

29       function initialize(
30           string memory _tokenName,
31           string memory _tokenSymbol,
32           uint8 _decimal
33:      ) external initializer {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L29-L33

Non-critical Issues

1. Return values of approve() not checked

Not all IERC20 implementations revert() when there's a failure in approve(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything

There are 2 instances of this issue:

File: src/contracts/Staking.sol   #1

79:               IERC20(TOKE_POOL).approve(CURVE_POOL, type(uint256).max);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L79

File: src/contracts/Staking.sol   #2

83:           IERC20(STAKING_TOKEN).approve(TOKE_POOL, type(uint256).max);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L83

2. Misleading variable names

There is 1 instance of this issue:

File: src/contracts/LiquidityReserve.sol   #1

/// @audit code is no longer FOX-specific
112:         uint256 lrFoxSupply = totalSupply();

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L112

3. public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There is 1 instance of this issue:

File: src/contracts/Staking.sol   #1

370:      function unstakeAllFromTokemak() public onlyOwner {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L370

4. constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 3 instances of this issue:

File: src/contracts/Staking.sol   #1

/// @audit 0x9008D19f58AAbD9eD0D60971565AA8510560ab41
73:           COW_SETTLEMENT = 0x9008D19f58AAbD9eD0D60971565AA8510560ab41;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L73

File: src/contracts/Staking.sol   #2

/// @audit 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110
74:           COW_RELAYER = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L74

File: src/contracts/Staking.sol   #3

/// @audit 12
76:           timeLeftToRequestWithdrawal = 12 hours;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L76

5. Use a more recent version of solidity

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions

There are 3 instances of this issue:

File: src/contracts/LiquidityReserve.sol   #1

2:    pragma solidity 0.8.9;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L2

File: src/contracts/Migration.sol   #2

2:    pragma solidity 0.8.9;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Migration.sol#L2

File: src/contracts/Staking.sol   #3

2:    pragma solidity 0.8.9;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L2

6. Typos

There are 10 instances of this issue:

File: src/contracts/Staking.sol

/// @audit withdrawRequest
315:          @notice creates a withdrawRequest with Tokemak

/// @audit requestedWithdraws
316:          @dev requestedWithdraws take 1 tokemak cycle to be available for withdraw

/// @audit cycleTime
348:          @notice checks TOKE's cycleTime is within duration to batch the transactions

/// @audit requestWithdraw
367:          @notice owner function to requestWithdraw all FOX from tokemak in case of an attack on tokemak

/// @audit requestWithdraw
368:          @dev this bypasses the normal flow of sending a withdrawal request and allows the owner to requestWithdraw entire pool balance

/// @audit asdf
675:          // prevent unstaking if override due to vulnerabilities asdf

/// @audit autoRebase
767:       * @dev this is function is called from claimFromTokemak if the autoRebase bool is set to true

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L315

File: src/contracts/Yieldy.sol

/// @audit profit_
74:           @notice increases Yieldy supply to increase staking balances relative to profit_

/// @audit co
232:          @notice called from the staking contract co create Yieldy tokens

/// @audit co
262:          @notice called from the staking contract co burn Yieldy tokens

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L74

7. NatSpec is incomplete

There are 2 instances of this issue:

File: src/contracts/BatchRequests.sol   #1

/// @audit Missing: '@param _index'
46        /**
47            @notice shows if contracts can batch by index
48            @return (address, bool)
49         */
50        function canBatchContractByIndex(uint256 _index)
51            external
52            view
53:           returns (address, bool)

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L46-L53

File: src/contracts/BatchRequests.sol   #2

/// @audit Missing: '@param _index'
61        /**
62            @notice get address in contracts by index
63            @return address
64         */
65:       function getAddressByIndex(uint256 _index) external view returns (address) {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L61-L65

8. Event is missing indexed fields

Each event should use three indexed fields if there are three or more fields

There are 12 instances of this issue:

File: src/contracts/LiquidityReserve.sol

21:       event FeeChanged(uint256 fee);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L21

File: src/contracts/Staking.sol

21:       event LogSetEpochDuration(uint256 indexed blockNumber, uint256 duration);

22:       event LogSetWarmUpPeriod(uint256 indexed blockNumber, uint256 period);

23:       event LogSetCoolDownPeriod(uint256 indexed blockNumber, uint256 period);

24:       event LogSetPauseStaking(uint256 indexed blockNumber, bool shouldPause);

25:       event LogSetPauseUnstaking(uint256 indexed blockNumber, bool shouldPause);

26        event LogSetPauseInstantUnstaking(
27            uint256 indexed blockNumber,
28            bool shouldPause
29:       );

30        event LogSetAffiliateAddress(
31            uint256 indexed blockNumber,
32            address affilateAddress
33:       );

34:       event LogSetAffiliateFee(uint256 indexed blockNumber, uint256 fee);

36:       event LogSetCurvePool(address indexed curvePool, int128 to, int128 from);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L21

File: src/contracts/Yieldy.sol

15        event LogSupply(
16            uint256 indexed epoch,
17            uint256 timestamp,
18            uint256 totalSupply
19:       );

21:       event LogRebase(uint256 indexed epoch, uint256 rebase, uint256 index);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L15-L19

#0 - moose-code

2022-09-05T10:27:14Z

Low risk issues: 1 - Agree 2 - Agree 3 - Agree 4 - Agree 5 - Agree 6 -Agree

Informational: 1- Agree 2 -Agree 3 - Agree 4 - Strongly agree, this is very helpful. 5 - Agree 6 - Agree 7 - Agree 8 - Agree

Summary

Gas Optimizations

IssueInstances
1Using calldata instead of memory for read-only arguments in external functions saves gas4
2Avoid contract existence checks by using solidity version 0.8.10 or later58
3State variables should be cached in stack variables rather than re-reading them from storage4
4internal functions only called once can be inlined to save gas7
5Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement3
6Using > 0 costs more gas than != 0 when used on a uint in a require() statement3
7It costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied2
8Splitting require() statements that use && saves gas6
9Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead6
10Duplicated require()/revert() checks should be refactored to a modifier or function3
11require() or revert() statements that check input arguments should be at the top of the function3
12Superfluous event fields9
13Use custom errors rather than revert()/require() strings to save gas37
14Functions guaranteed to revert when called by normal users can be marked payable22

Total: 167 instances over 14 issues

Gas Optimizations

1. Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

There are 4 instances of this issue:

File: src/contracts/LiquidityReserve.sol   #1

37:           string memory _tokenName,

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L37

File: src/contracts/LiquidityReserve.sol   #2

38:           string memory _tokenSymbol,

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L38

File: src/contracts/Yieldy.sol   #3

30:           string memory _tokenName,

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L30

File: src/contracts/Yieldy.sol   #4

31:           string memory _tokenSymbol,

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L31

2. Avoid contract existence checks by using solidity version 0.8.10 or later

Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (700 gas), to check for contract existence for external calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value

There are 58 instances of this issue:

File: src/contracts/BatchRequests.sol

/// @audit canBatchTransactions()
19:                   IStaking(contracts[i]).canBatchTransactions()

/// @audit canBatchTransactions()
37:               bool canBatch = IStaking(contracts[i]).canBatchTransactions();

/// @audit canBatchTransactions()
57:               IStaking(contracts[_index]).canBatchTransactions()

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L19

File: src/contracts/LiquidityReserve.sol

/// @audit balanceOf()
64:           uint256 stakingTokenBalance = IERC20Upgradeable(stakingToken).balanceOf(

/// @audit safeTransferFrom()
75:           IERC20Upgradeable(stakingToken).safeTransferFrom(

/// @audit approve()
81:           IERC20Upgradeable(rewardToken).approve(

/// @audit balanceOf()
106:          uint256 stakingTokenBalance = IERC20Upgradeable(stakingToken).balanceOf(

/// @audit balanceOf()
109:          uint256 rewardTokenBalance = IERC20Upgradeable(rewardToken).balanceOf(

/// @audit coolDownInfo()
113           uint256 coolDownAmount = IStaking(stakingContract)
114:              .coolDownInfo(address(this))

/// @audit safeTransferFrom()
121:          IERC20Upgradeable(stakingToken).safeTransferFrom(

/// @audit balanceOf()
140:          uint256 stakingTokenBalance = IERC20Upgradeable(stakingToken).balanceOf(

/// @audit balanceOf()
143:          uint256 rewardTokenBalance = IERC20Upgradeable(rewardToken).balanceOf(

/// @audit coolDownInfo()
146           uint256 coolDownAmount = IStaking(stakingContract)
147:              .coolDownInfo(address(this))

/// @audit balanceOf()
171:              IERC20Upgradeable(stakingToken).balanceOf(address(this)) >=

/// @audit safeTransfer()
177:          IERC20Upgradeable(stakingToken).safeTransfer(

/// @audit safeTransferFrom()
198:          IERC20Upgradeable(rewardToken).safeTransferFrom(

/// @audit safeTransfer()
204:          IERC20Upgradeable(stakingToken).safeTransfer(

/// @audit coolDownInfo()
216           uint256 coolDownAmount = IStaking(stakingContract)
217:              .coolDownInfo(address(this))

/// @audit balanceOf()
220:              uint256 amount = IERC20Upgradeable(rewardToken).balanceOf(

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L64

File: src/contracts/Migration.sol

/// @audit REWARD_TOKEN()
28:           OLD_YIELDY_TOKEN = IStakingV1(_oldContract).REWARD_TOKEN();

/// @audit STAKING_TOKEN()
29:           address stakingToken = IStaking(_newContract).STAKING_TOKEN();

/// @audit approve()
31:           IYieldy(OLD_YIELDY_TOKEN).approve(_oldContract, type(uint256).max);

/// @audit approve()
32:           IERC20Upgradeable(stakingToken).approve(

/// @audit balanceOf()
44:           uint256 userWalletBalance = IYieldy(OLD_YIELDY_TOKEN).balanceOf(

/// @audit transferFrom()
48:           IYieldy(OLD_YIELDY_TOKEN).transferFrom(

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Migration.sol#L28

File: src/contracts/Staking.sol

/// @audit approve()
79:               IERC20(TOKE_POOL).approve(CURVE_POOL, type(uint256).max);

/// @audit approve()
83:           IERC20(STAKING_TOKEN).approve(TOKE_POOL, type(uint256).max);

/// @audit approve()
84:           IERC20Upgradeable(YIELDY_TOKEN).approve(

/// @audit approve()
88:           IERC20Upgradeable(YIELDY_TOKEN).approve(

/// @audit approve()
92:           IERC20Upgradeable(TOKE_TOKEN).approve(COW_RELAYER, type(uint256).max);

/// @audit safeTransfer()
132:              IERC20Upgradeable(TOKE_TOKEN).safeTransfer(FEE_ADDRESS, feeAmount);

/// @audit balanceOf()
144:          uint256 totalTokeAmount = IERC20Upgradeable(TOKE_TOKEN).balanceOf(

/// @audit safeTransfer()
147:          IERC20Upgradeable(TOKE_TOKEN).safeTransfer(

/// @audit balanceOf()
321:          uint256 balance = ITokePool(TOKE_POOL).balanceOf(address(this));

/// @audit balanceOf()
372:          uint256 tokePoolBalance = ITokePool(tokePoolContract).balanceOf(

/// @audit totalSupply()
412:          uint256 yieldyTotalSupply = IYieldy(YIELDY_TOKEN).totalSupply();

/// @audit safeTransferFrom()
419:          IERC20Upgradeable(STAKING_TOKEN).safeTransferFrom(

/// @audit creditsForTokenBalance()
442:                      IYieldy(YIELDY_TOKEN).creditsForTokenBalance(_amount),

/// @audit transfer()
471:                  IYieldy(YIELDY_TOKEN).transfer(

/// @audit tokenBalanceForCredits()
473:                      IYieldy(YIELDY_TOKEN).tokenBalanceForCredits(info.credits)

/// @audit tokenBalanceForCredits()
487           uint256 totalAmountIncludingRewards = IYieldy(YIELDY_TOKEN)
488:              .tokenBalanceForCredits(info.credits);

/// @audit safeTransfer()
498:              IERC20Upgradeable(STAKING_TOKEN).safeTransfer(

/// @audit balanceOf()
519:          uint256 walletBalance = IERC20Upgradeable(YIELDY_TOKEN).balanceOf(

/// @audit tokenBalanceForCredits()
522:          uint256 warmUpBalance = IYieldy(YIELDY_TOKEN).tokenBalanceForCredits(

/// @audit creditsForTokenBalance()
545:                      IYieldy(YIELDY_TOKEN).creditsForTokenBalance(_amount);

/// @audit tokenBalanceForCredits()
546                   uint256 remainingAmount = IYieldy(YIELDY_TOKEN)
547:                      .tokenBalanceForCredits(remainingCreditsAmount);

/// @audit safeTransferFrom()
559:              IERC20Upgradeable(YIELDY_TOKEN).safeTransferFrom(

/// @audit balanceOf()
582:          uint256 reserveBalance = IERC20Upgradeable(STAKING_TOKEN).balanceOf(

/// @audit exchange()
620:              ICurvePool(CURVE_POOL).exchange(

/// @audit coins()
634:              address address0 = ICurvePool(CURVE_POOL).coins(0);

/// @audit coins()
635:              address address1 = ICurvePool(CURVE_POOL).coins(1);

/// @audit get_dy()
664:              ICurvePool(CURVE_POOL).get_dy(curvePoolFrom, curvePoolTo, _amount);

/// @audit creditsForTokenBalance()
690:                  IYieldy(YIELDY_TOKEN).creditsForTokenBalance(_amount),

/// @audit totalSupply()
711:              uint256 staked = IYieldy(YIELDY_TOKEN).totalSupply();

/// @audit balanceOf()
730:              IERC20Upgradeable(STAKING_TOKEN).balanceOf(address(this)) +

/// @audit safeTransferFrom()
747:              IERC20Upgradeable(STAKING_TOKEN).safeTransferFrom(

/// @audit balanceOf()
755           uint256 stakingTokenBalance = IERC20Upgradeable(STAKING_TOKEN)
756:              .balanceOf(address(this));

/// @audit setPreSignature()
770:          ICowSettlement(COW_SETTLEMENT).setPreSignature(orderUid, true);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L79

3. State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There are 4 instances of this issue:

File: src/contracts/BatchRequests.sol   #1

/// @audit contracts on line 18
19:                   IStaking(contracts[i]).canBatchTransactions()

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L19

File: src/contracts/BatchRequests.sol   #2

/// @audit contracts on line 19
21:                   IStaking(contracts[i]).sendWithdrawalRequests();

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L21

File: src/contracts/BatchRequests.sol   #3

/// @audit contracts on line 37
38:               batch[i] = Batch(contracts[i], canBatch);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L38

File: src/contracts/BatchRequests.sol   #4

/// @audit contracts on line 56
57:               IStaking(contracts[_index]).canBatchTransactions()

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L57

4. internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 7 instances of this issue:

File: src/contracts/LiquidityReserve.sol

134       function _calculateReserveTokenValue(uint256 _amount)
135           internal
136           view
137:          returns (uint256)

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L134-L137

File: src/contracts/Staking.sol

129:      function _sendAffiliateFee(uint256 _amount) internal {

274       function _isClaimWithdrawAvailable(address _recipient)
275           internal
276:          returns (bool)

342:      function _getTokemakBalance() internal view returns (uint256) {

727:      function contractBalance() internal view returns (uint256) {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L129

File: src/contracts/Yieldy.sol

69:       function _setIndex(uint256 _index) internal {

110       function _storeRebase(
111           uint256 _previousCirculating,
112           uint256 _profit,
113:          uint256 _epoch

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L69

5. Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a }

There are 3 instances of this issue:

File: src/contracts/LiquidityReserve.sol   #1

/// @audit require() on line 94
196:         uint256 amountMinusFee = _amount - ((_amount * fee) / BASIS_POINTS);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L196

File: src/contracts/Yieldy.sol   #2

/// @audit require() on line 190
192:          creditBalances[msg.sender] = creditBalances[msg.sender] - creditAmount;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L192

File: src/contracts/Yieldy.sol   #3

/// @audit require() on line 210
212:          uint256 newValue = _allowances[_from][msg.sender] - _value;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L212

6. Using > 0 costs more gas than != 0 when used on a uint in a require() statement

This change saves 6 gas per instance. The optimization works until solidity version 0.8.13 where there is a regression in gas costs.

There are 3 instances of this issue:

File: src/contracts/Staking.sol   #1

410:          require(_amount > 0, "Must have valid amount");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L410

File: src/contracts/Staking.sol   #2

572:          require(_amount > 0, "Invalid amount");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L572

File: src/contracts/Staking.sol   #3

604:          require(_amount > 0, "Invalid amount");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L604

7. It costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied

Not overwriting the default for stack variables saves 8 gas. Storage and memory variables have larger savings

There are 2 instances of this issue:

File: src/contracts/Staking.sol   #1

636:              int128 from = 0;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L636

File: src/contracts/Staking.sol   #2

637:              int128 to = 0;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L637

8. Splitting require() statements that use && saves gas

See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper

There are 6 instances of this issue:

File: src/contracts/LiquidityReserve.sol

44            require(
45                _stakingToken != address(0) && _rewardToken != address(0),
46                "Invalid address"
47:           );

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L44-L47

File: src/contracts/Migration.sol

20            require(
21                _oldContract != address(0) && _newContract != address(0),
22                "Invalid address"
23:           );

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Migration.sol#L20-L23

File: src/contracts/Staking.sol

54            require(
55                _stakingToken != address(0) &&
56                    _yieldyToken != address(0) &&
57                    _tokeToken != address(0) &&
58                    _tokePool != address(0) &&
59                    _tokeManager != address(0) &&
60                    _tokeReward != address(0) &&
61                    _liquidityReserve != address(0),
62                "Invalid address"
63:           );

574           require(
575               !isUnstakingPaused && !isInstantUnstakingPaused,
576               "Unstaking is paused"
577:          );

605           require(
606               CURVE_POOL != address(0) &&
607                   (curvePoolFrom == 1 || curvePoolTo == 1),
608               "Invalid Curve Pool"
609:          );

611           require(
612               !isUnstakingPaused && !isInstantUnstakingPaused,
613               "Unstaking is paused"
614:          );

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L54-L63

9. Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed

There are 6 instances of this issue:

File: src/contracts/Staking.sol

36:       event LogSetCurvePool(address indexed curvePool, int128 to, int128 from);

36:       event LogSetCurvePool(address indexed curvePool, int128 to, int128 from);

113:          uint8 _v,

636:              int128 from = 0;

637:              int128 to = 0;

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L36

File: src/contracts/Yieldy.sol

32:           uint8 _decimal

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L32

10. Duplicated require()/revert() checks should be refactored to a modifier or function

Saves deployment costs

There are 3 instances of this issue:

File: src/contracts/LiquidityReserve.sol   #1

192:          require(isReserveEnabled, "Not enabled yet");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L192

File: src/contracts/Staking.sol   #2

604:          require(_amount > 0, "Invalid amount");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L604

File: src/contracts/Staking.sol   #3

611           require(
612               !isUnstakingPaused && !isInstantUnstakingPaused,
613               "Unstaking is paused"
614:          );

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L611-L614

11. require() or revert() statements that check input arguments should be at the top of the function

Checks that involve constants should come before checks that involve state variables

There are 3 instances of this issue:

File: src/contracts/LiquidityReserve.sol   #1

62:           require(_stakingContract != address(0), "Invalid address");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L62

File: src/contracts/Staking.sol   #2

410:          require(_amount > 0, "Must have valid amount");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L410

File: src/contracts/Yieldy.sol   #3

59:           require(_stakingContract != address(0), "Invalid address");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L59

12. Superfluous event fields

block.timestamp and block.number are added to event information by default so adding them manually wastes gas

There are 9 instances of this issue:

File: src/contracts/Staking.sol

21:       event LogSetEpochDuration(uint256 indexed blockNumber, uint256 duration);

22:       event LogSetWarmUpPeriod(uint256 indexed blockNumber, uint256 period);

23:       event LogSetCoolDownPeriod(uint256 indexed blockNumber, uint256 period);

24:       event LogSetPauseStaking(uint256 indexed blockNumber, bool shouldPause);

25:       event LogSetPauseUnstaking(uint256 indexed blockNumber, bool shouldPause);

27:           uint256 indexed blockNumber,

31:           uint256 indexed blockNumber,

34:       event LogSetAffiliateFee(uint256 indexed blockNumber, uint256 fee);

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L21

File: src/contracts/Yieldy.sol

17:           uint256 timestamp,

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L17

13. Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 37 instances of this issue:

File: src/contracts/LiquidityReserve.sol

25:           require(msg.sender == stakingContract, "Not staking contract");

44            require(
45                _stakingToken != address(0) && _rewardToken != address(0),
46                "Invalid address"
47:           );

61:           require(!isReserveEnabled, "Already enabled");

62:           require(_stakingContract != address(0), "Invalid address");

68            require(
69                stakingTokenBalance >= MINIMUM_LIQUIDITY,
70                "Not enough staking tokens"
71:           );

94:           require(_fee <= BASIS_POINTS, "Out of range");

105:          require(isReserveEnabled, "Not enabled yet");

163:          require(_amount <= balanceOf(msg.sender), "Not enough lr tokens");

170           require(
171               IERC20Upgradeable(stakingToken).balanceOf(address(this)) >=
172                   amountToWithdraw,
173               "Not enough funds"
174:          );

192:          require(isReserveEnabled, "Not enabled yet");

215:          require(isReserveEnabled, "Not enabled yet");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L25

File: src/contracts/Migration.sol

20            require(
21                _oldContract != address(0) && _newContract != address(0),
22                "Invalid address"
23:           );

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Migration.sol#L20-L23

File: src/contracts/Staking.sol

54            require(
55                _stakingToken != address(0) &&
56                    _yieldyToken != address(0) &&
57                    _tokeToken != address(0) &&
58                    _tokePool != address(0) &&
59                    _tokeManager != address(0) &&
60                    _tokeReward != address(0) &&
61                    _liquidityReserve != address(0),
62                "Invalid address"
63:           );

118:          require(_recipient.amount > 0, "Must enter valid amount");

143:          require(_claimAddress != address(0), "Invalid address");

408:          require(!isStakingPaused, "Staking is paused");

410:          require(_amount > 0, "Must have valid amount");

527           require(
528               _amount <= walletBalance + warmUpBalance,
529               "Insufficient Balance"
530:          );

572:          require(_amount > 0, "Invalid amount");

574           require(
575               !isUnstakingPaused && !isInstantUnstakingPaused,
576               "Unstaking is paused"
577:          );

586:          require(reserveBalance >= _amount, "Not enough funds in reserve");

604:          require(_amount > 0, "Invalid amount");

605           require(
606               CURVE_POOL != address(0) &&
607                   (curvePoolFrom == 1 || curvePoolTo == 1),
608               "Invalid Curve Pool"
609:          );

611           require(
612               !isUnstakingPaused && !isInstantUnstakingPaused,
613               "Unstaking is paused"
614:          );

644:              require(from == 1 || to == 1, "Invalid Curve Pool");

676:          require(!isUnstakingPaused, "Unstaking is paused");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L54-L63

File: src/contracts/Yieldy.sol

58:           require(stakingContract == address(0), "Already Initialized");

59:           require(_stakingContract != address(0), "Invalid address");

83:           require(_totalSupply > 0, "Can't rebase if not circulating");

96:               require(rebasingCreditsPerToken > 0, "Invalid change in supply");

187:          require(_to != address(0), "Invalid address");

190:          require(creditAmount <= creditBalances[msg.sender], "Not enough funds");

210:          require(_allowances[_from][msg.sender] >= _value, "Allowance too low");

249:          require(_address != address(0), "Mint to the zero address");

257:          require(_totalSupply < MAX_SUPPLY, "Max supply");

279:          require(_address != address(0), "Burn from the zero address");

286:          require(currentCredits >= creditAmount, "Not enough balance");

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L58

14. Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 22 instances of this issue:

File: src/contracts/BatchRequests.sol

81:       function addAddress(address _address) external onlyOwner {

89:       function removeAddress(address _address) external onlyOwner {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/BatchRequests.sol#L81

File: src/contracts/LiquidityReserve.sol

57        function enableLiquidityReserve(address _stakingContract)
58            external
59:           onlyOwner

92:       function setFee(uint256 _fee) external onlyOwner {

188       function instantUnstake(uint256 _amount, address _recipient)
189           external
190:          onlyStakingContract

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/LiquidityReserve.sol#L57-L59

File: src/contracts/Staking.sol

141:      function transferToke(address _claimAddress) external onlyOwner {

157:      function setCurvePool(address _curvePool) external onlyOwner {

167:      function setAffiliateFee(uint256 _affiliateFee) external onlyOwner {

177:      function setAffiliateAddress(address _affiliateAddress) external onlyOwner {

187:      function shouldPauseStaking(bool _shouldPause) public onlyOwner {

197:      function shouldPauseUnstaking(bool _shouldPause) external onlyOwner {

207:      function shouldPauseInstantUnstaking(bool _shouldPause) external onlyOwner {

217:      function setEpochDuration(uint256 duration) external onlyOwner {

226:      function setWarmUpPeriod(uint256 _vestingPeriod) external onlyOwner {

235:      function setCoolDownPeriod(uint256 _vestingPeriod) external onlyOwner {

246       function setTimeLeftToRequestWithdrawal(uint256 _timestamp)
247           external
248:          onlyOwner

370:      function unstakeAllFromTokemak() public onlyOwner {

769:      function preSign(bytes calldata orderUid) external onlyOwner {

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Staking.sol#L141

File: src/contracts/Yieldy.sol

54        function initializeStakingContract(address _stakingContract)
55            external
56:           onlyRole(ADMIN_ROLE)

78        function rebase(uint256 _profit, uint256 _epoch)
79            external
80:           onlyRole(REBASE_ROLE)

236       function mint(address _address, uint256 _amount)
237           external
238:          onlyRole(MINTER_BURNER_ROLE)

266       function burn(address _address, uint256 _amount)
267           external
268:          onlyRole(MINTER_BURNER_ROLE)

https://github.com/code-423n4/2022-06-yieldy/blob/524f3b83522125fb7d4677fa7a7e5ba5a2c0fe67/src/contracts/Yieldy.sol#L54-L56

#0 - moose-code

2022-07-15T13:40:12Z

Power

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