Platform: Code4rena
Start Date: 12/08/2021
Pot Size: $30,000 USDC
Total HM: 9
Participants: 8
Period: 3 days
Judge: ghoulsol
Total Solo HM: 7
Id: 25
League: ETH
Rank: 4/8
Findings: 2
Award: $2,366.82
π Selected for report: 12
π Solo Findings: 0
π Selected for report: 0xRajeev
390.7857 USDC - $390.79
0xRajeev
solc version 0.8.3 and 0.8.4 fixed important bugs in the compiler. Using version 0.8.1 misses these fixes and may cause a vulnerability.
https://github.com/ethereum/solidity/releases/tag/v0.8.4: Solidity 0.8.4 fixes a bug in the ABI decoder. The release contains an important bugfix. SeeΒ decoding from memory bugΒ blog post for more details.
https://github.com/ethereum/solidity/releases/tag/v0.8.3: Solidity 0.8.3 is a bugfix release that fixes an important bug about how the optimizer handles the Keccak256 opcode. For details on the bug, please see theΒ bug blog post.
Manual Analysis
Consider upgrading to 0.8.3 or 0.8.4
#1 - alcueca
2021-08-18T16:03:05Z
I might actually revert the fixes, unless we are affected by the bug fixes. Using solc 0.8.6 forces us to drop the optimizer from 20000 to 5000.
175.8536 USDC - $175.85
0xRajeev
Few events are missing emits which prevents the intended data from being observed easily by off-chain interfaces.
Manual Analysis
Add emits or remove event declarations.
#0 - alcueca
2021-08-16T11:28:58Z
π Selected for report: 0xRajeev
390.7857 USDC - $390.79
0xRajeev
That cauldron_ parameter is not used here and ladle_.cauldron() is used instead. The Ladle constructor initializes its cauldron value and so the only way this could differ from the parameter is if the argument to this function is specified incorrectly.
Manual Analysis
Either use parameter or remove it in favor of the value from ladle_.cauldron().
#0 - alcueca
2021-08-16T11:28:32Z
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
The declaration order of state variables affects storage slot packing and gas impact from reads/writes of shared slots.
The 8B rewardsPeriod struct gets packed with the 20B rewardsToken and may result in increased gas usage at runtime due to masked reads/writes. Declaring it instead with uint128 start and end times will force it to occupy a full slot and result in gas savings.
Other declarations: https://github.com/code-423n4/2021-08-yield/blob/4dc46470e616dd0cbd9db9b4742e36c4d809e02c/contracts/yieldspace/Strategy.sol#L51-L65
Manual Analysis
Reconsider the declaration order of state variables and the use of variables with size less than word size of 256-bits. Both these affect gas efficiency.
#0 - alcueca
2021-08-15T15:08:45Z
Whoa, I had never thought of that. Thanks!
#1 - alcueca
2021-08-16T07:37:44Z
Gas savings proven to be minimal, I'll leave it as it is for cleaner code.
37.9213 USDC - $37.92
0xRajeev
Public functions need to copy their arguments from calldata to memory resulting in more bytecode and gas consumption. If functions are never called from within the contracts, they can be declared external in which case their parameters are always in calldata without being copied to memory. This results in gas savings. There are many such public functions that donβt appear to be called from within the contract.
All functions declared in this range: https://github.com/code-423n4/2021-08-yield/blob/4dc46470e616dd0cbd9db9b4742e36c4d809e02c/contracts/yieldspace/Strategy.sol#L127-L252
Manual Analysis
Change function visibility from public to externalβ¨
#0 - alcueca
2021-08-16T07:22:59Z
#1 - alcueca
2021-08-16T11:28:05Z
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
SLOADs cost 2100 gas for first time reads of state variables and then 100 gas for repeated reads in the context of a transaction (post Berlin fork). MLOADs cost 3 gas units. Therefore, caching state variable in local variables for repeated reads saves gas.
Examples of state variables that are read at the lines shown and also later in that same function:
rewardsPeriod: https://github.com/code-423n4/2021-08-yield/blob/4dc46470e616dd0cbd9db9b4742e36c4d809e02c/contracts/utils/token/ERC20Rewards.sol#L80
pool (600 gas savings): https://github.com/code-423n4/2021-08-yield/blob/4dc46470e616dd0cbd9db9b4742e36c4d809e02c/contracts/yieldspace/Strategy.sol#L183
Manual Analysis
Consider caching state variables in local variables.
#0 - alcueca
2021-08-16T07:22:19Z
Fix for ERC20Rewards - _totalSupply Not optimizing for gas in governance actions when it adds lines.
#1 - alcueca
2021-08-16T11:30:06Z
Fix (Strategy) and another fix
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
Event emits where there are equivalent local variables or parameters for state variables can save gas by using those instead of state variables because of the expensive SLOADs.
Manual Analysis
Use parameters or local variables instead of state variables in event emits
#0 - alcueca
2021-08-16T07:09:52Z
#1 - alcueca
2021-08-16T07:18:11Z
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
Function parameters are passed in calldata. For external functions, these are simply read from calldata. But explicitly specifying memory location for such parameters will force their copying to memory resulting in extra bytecode and more gas. Leaving them in calldata will save gas.
Manual Analysis
Do not use memory data location specifier for external function parameters
#0 - alcueca
2021-08-16T10:25:32Z
#1 - alcueca
2021-08-16T12:15:55Z
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
As noted in the code comment, peek and get functions are the same for this oracle. So we can change peek
to public visibility and have get
call peek
instead of copying the same code here. Minor deployment cost savings but increase in readability/maintainability.
Manual Analysis
Replace two functions having the same code with a single function.
#0 - alcueca
2021-08-15T15:15:42Z
Instead I'll do an internal _peek
function called by both peek
and get
, but thanks.
#1 - alcueca
2021-08-17T17:30:37Z
Actually, it was the natspec that was wrong. CompositeMultiOracle actually has different code for get
and peek
.
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
The require check for decimals_ <= 18 is unnecessary given its set to 18 right above unless this needs to be obtained differently as hinted by the comment.
Manual Analysis
Evaluate check and remove.
#0 - alcueca
2021-08-15T15:17:02Z
Quite a silly one, isn't it?
#1 - alcueca
2021-08-18T06:18:14Z
π Selected for report: 0xRajeev
84.2697 USDC - $84.27
0xRajeev
The check for array lengths is unnecessary in two places where the following check on txHash will anyway fail if the lengths donβt match with what was hashed earlier during schedule. Removing the length check can save a little gas.
Such a require() in cancel() can be removed because if there is a mismatch, the entry lookup in transactions[] will fail anyway and also, this will not be sceduled/executed.
Manual Analysis
Evaluate and remove these checks.
#0 - alcueca
2021-08-16T09:56:44Z
#1 - alcueca
2021-08-16T10:27:23Z
π Selected for report: 0xRajeev
390.7857 USDC - $390.79
0xRajeev
setRewards() is missing input validation on parameters start and end to check if end > start. If accidentally set incorrectly, this will allow resetting new rewards while there is an ongoing one.
Manual Analysis
Add a require() to check that end > start.
#0 - alcueca
2021-08-15T15:21:10Z
If accidentally set incorrectly, this will allow resetting new rewards while there is an ongoing one.
I would say that if we set it incorrectly, we would like to reset it as soon as possible :)
Still, a good check to add, since otherwise it leads to strange behaviour.
#1 - alcueca
2021-08-16T07:07:53Z