Platform: Code4rena
Start Date: 27/05/2022
Pot Size: $75,000 USDC
Total HM: 20
Participants: 58
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 15
Id: 131
League: ETH
Rank: 13/58
Findings: 4
Award: $2,143.98
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xNazgul, 0xf15ers, BowTiedWardens, Chom, Funen, Kaiziron, Kumpa, MiloTruck, Picodes, Ruhum, SecureZeroX, Sm4rty, SmartSek, StyxRave, WatchPug, Waze, asutorufos, bardamu, berndartmueller, c3phas, catchup, cccz, codexploder, cryptphi, defsec, delfin454000, dipp, fatherOfBlocks, gzeon, hake, hansfriese, hyh, masterchief, oyc_109, sach1r0, sashik_eth, shenwilly, simon135, unforgiven
113.8755 USDC - $113.88
Approve
not compatible with Tether (USDT) implementationSome tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT or CVX)'s approve()
function will revert if the current approval is not zero, to protect against front-running changes of approvals.
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) { // To change the approve amount you first have to reduce the addresses` // allowance to zero by calling `approve(_spender, 0)` if it is not // already 0 to mitigate the race condition described here: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 require(!((_value != 0) && (allowed[msg.sender][_spender] != 0))); allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); }
The code as currently implemented does not handle these sorts of tokens properly, which would prevent USDT or CVX, from being used by this project.
If the method allPools return pools with duplicate underlyings, it will fault in the case of USDT or CVX, for example.
Affected source code:
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:
If you set any wrong address, without being a contract, an EOA for example, it cannot be changed again:
Lack of int range checks:
Not max defined:
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:
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:
grantRole
and revokeRole
.The RoleManager
contract contains a few special roles (Roles.ADDRESS_PROVIDER
, Roles.POOL_FACTORY
, Roles.CONTROLLER
, Roles.POOL
and Roles.VAULT
) that are not controlled in the grantRole
and revokeRole
methods.
Affected source code:
The initialize
method of the BkdLocker
contract allows it to be started multiple times as long as the value startBoost=0
is set. Abuse these settings to his advantage.
Affected source code:
The minter
address can mint
arbitrary amount of tokens. 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.
Affected source code:
The FeeBurner
contract sets the swapperRouter
in the _addressProvider
, so the owner can set any type of swapper, paths or pools, even malicious ones.
Since there is no slippage defined in the FeeBurner
contract itself, it could be that a swapperRouter
returns 0 WETH, and keeps the sent tokens.
Affected source code:
#0 - GalloDaSballo
2022-06-19T22:47:03Z
Invalid as the approval happen once, from zero, to the max
Valid per industry standard
##Â Lack of ACK during owner change Personally disagree, but completely acceptable suggestion
I believe this finding to be invalid in lack of acknowledging the broader system
Idem
Dup of #136 Which I've yet to judge (may raise severity of this one)
Observation is valid, but lacks nuance as the minter is a smart contract under scope
Agree with this finding and may raise severity as well as Duplicate of #113
#1 - GalloDaSballo
2022-06-19T22:47:39Z
Overall most findings are pretty common from CodeArena, however the format is short and to the point, which I appreciate especially for the more basic findings
#2 - GalloDaSballo
2022-06-22T14:45:38Z
Will raise Multiple initialization and Controlled swapRouter
To 2 separate medium findings
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, Chom, Dravee, Fitraldys, Funen, Kaiziron, MiloTruck, Picodes, Randyyy, RoiEvenHaim, SecureZeroX, Sm4rty, SmartSek, StyxRave, Tadashi, Tomio, Waze, asutorufos, berndartmueller, c3phas, catchup, csanuragjain, defsec, delfin454000, djxploit, fatherOfBlocks, gzeon, hake, hansfriese, oyc_109, robee, sach1r0, sashik_eth, scaraven, simon135
62.6774 USDC - $62.68
The pragma version used is pragma solidity 0.8.10;
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
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).
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
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:
Use delete
instead of set to default value (false
or 0
)
Affected source code:
It's possible to save gas using the unckecked
keyword around the i
variable. This will avoid the required checks to ensure that the variable won't overflow, and in the case of this for loop, is almost impossible:
Reference:
Affected source code:
++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--
.
Also, in some of the points mentioned below, it would be necessary to add unckecked
since said variable is impossible to overflow.
Affected source code:
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:
It's possible to optimize the method getRoleMember
of the contract RoleManager
checking first if the index
is 0.
Before:
if (role == Roles.ADDRESS_PROVIDER && index == 0) { return address(addressProvider); } else if (role == Roles.POOL_FACTORY && index == 0) { return addressProvider.getAddress(AddressProviderKeys._POOL_FACTORY_KEY); } else if (role == Roles.CONTROLLER && index == 0) { return addressProvider.getAddress(AddressProviderKeys._CONTROLLER_KEY); } else if (role == Roles.POOL) { return addressProvider.getPoolAtIndex(index); } else if (role == Roles.VAULT) { return addressProvider.getVaultAtIndex(index); }
After:
if (index == 0) { if (role == Roles.ADDRESS_PROVIDER) return address(addressProvider); if (role == Roles.POOL_FACTORY) return addressProvider.getAddress(AddressProviderKeys._POOL_FACTORY_KEY); if (role == Roles.CONTROLLER) return addressProvider.getAddress(AddressProviderKeys._CONTROLLER_KEY); } if (role == Roles.POOL) return addressProvider.getPoolAtIndex(index); if (role == Roles.VAULT) return addressProvider.getVaultAtIndex(index);
Note that also, this change will increase readability.
Affected source code:
El metodo uncheckedInc
de la libreria UncheckedMath
pretenden ahorrar gas, pero suponiendo que el compilador lo optimize al ser mayor a 0.8.2 y se compile como inline, aun asà devolverá un valor a la pila que puede ser evitado. Y además es más optimo utiliza ++a en lugar de a+1;
The uncheckedInc
method of the UncheckedMath
library is intended to save gas, but assuming the compiler optimizes it because it's greater than 0.8.2, it will still return a value on the stack that can be avoided. And it is also more optimal to use ++a
instead of a + 1;
.
Affected source code:
#0 - chase-manning
2022-06-08T08:06:58Z
I consider this report to be of particularly high quality
#1 - GalloDaSballo
2022-06-15T23:24:54Z
Outdated compiler Not a gas saving
Reduce the size of error messages (Long revert Strings)
Finding is valid, in lack of a list of the messages that are too long I'll give 1 for Error.sol
and 1 for Minter.sol
Per the discussion the warden specified saving an MSTORE would save 3 gas, computing an offset would most likely be another 3, so for the sake of this contest I'll give 6 gas saved per revert message.
12 gas
Delete optimization
Delete internally sets the value to 0, so I do not believe this will save gas, to confirm. delete xyz
is equivalent to xyz = 0
unchecked keyword Agree it would save around 20 gas
++i
Personally I think inlining unchecked { ++i }
would net the highest savings.
That said I believe that just doing ++i would save 3 gas and not 5 as the warden said. EDIT: I ran tests and it's 5 gas
19 * 5 = 95
Avoid unused return Agree that it would save gas, let's say 3 gas. It would also save the cost of verifying the return value which would most likely be around 6 gas (3 to check + 3 to check offset)
41 * 3 = 123
Improve logic This should save gas in some cases
Inline improve Agree that inlining would save more gas, let's say 3 as the intermediary result from the library should still be inlined per this discussion: https://github.com/ethereum/solidity/issues/3985#issuecomment-383848718
3 * 19 = 57
Total Gas Saved: 307