Platform: Code4rena
Start Date: 08/06/2022
Pot Size: $115,000 USDC
Total HM: 26
Participants: 72
Period: 11 days
Judge: leastwood
Total Solo HM: 14
Id: 132
League: ETH
Rank: 5/72
Findings: 4
Award: $4,292.44
🌟 Selected for report: 0
🚀 Solo Findings: 0
Integer overflow can affect router balances.
The repayAavePortal
method of the PortalFacet
contract subtracts the balance within an unchecked
region, but this balance is not checked beforehand to be greater than the amountIn
.
unchecked { s.routerBalances[msg.sender][_local] -= amountIn; }
The unique check against the balance is with the _maxIn
that always could be greater than the balance because apparently is use as slippage (is not documented as a @param
in the methods).
if (routerBalance < _maxIn) revert PortalFacet__repayAavePortal_insufficientFunds();
Remove the unchecked
region.
#0 - ecmendenhall
2022-06-20T04:48:25Z
Duplicate of #68
#1 - LayneHaber
2022-06-21T22:35:27Z
Closing in favor of #68
1169.1572 USDC - $1,169.16
https://github.com/code-423n4/2022-06-connext/blob/07adce8e88d3c93e26d32c9c2056593c62911197/contracts/contracts/core/connext/helpers/ConnextPriceOracle.sol#L103 https://github.com/code-423n4/2022-06-connext/blob/07adce8e88d3c93e26d32c9c2056593c62911197/contracts/contracts/core/connext/helpers/ConnextPriceOracle.sol#L134 https://github.com/code-423n4/2022-06-connext/blob/07adce8e88d3c93e26d32c9c2056593c62911197/contracts/contracts/core/connext/facets/StableSwapFacet.sol#L426
Some contracts assumes decimals <= 18 and does not handle > 18 decimals.
Because the pragma used doesn't allow integer underflows, if a token with more than 18 decimals is used, an integer underflow will produce a denial of service.
Some tokens have more than 18 decimals (e.g. YAM-V2 has 24).
This may trigger unexpected reverts due to overflow, posing a liveness risk to the contract.
Reference:
Major severity finding from Consensys Diligence Audit of Defi Saver:
Affected source code:
Ascertain that the code will not fail if the token's decimals are greater than 18.
#0 - ecmendenhall
2022-06-20T15:14:35Z
#1 - ecmendenhall
2022-06-20T15:16:06Z
I gave this a ❤️ along with https://github.com/code-423n4/2022-06-connext-findings/issues/204 because these findings both identified an additional location in the StableSwap contract where the 18 decimal assumption is hardcoded.
#2 - jakekidd
2022-06-25T02:36:44Z
Marking this as duplicate but leaving it open/confirmed since it identifies that additional location where the assumption is hardcoded!
#3 - jakekidd
2022-06-27T03:39:34Z
Assortment of findings across these three issues: https://github.com/code-423n4/2022-06-connext-findings/issues/39 https://github.com/code-423n4/2022-06-connext-findings/issues/61 https://github.com/code-423n4/2022-06-connext-findings/issues/204
#4 - jakekidd
2022-06-29T20:16:09Z
#5 - 0xleastwood
2022-08-13T23:34:49Z
Duplicate of #204.
🌟 Selected for report: BowTiedWardens
Also found by: 0x1f8b, 0x29A, 0x52, 0xNazgul, 0xNineDec, 0xf15ers, 0xkatana, 0xmint, Chom, ElKu, Funen, IllIllI, JMukesh, Jujic, Kaiziron, Lambda, MiloTruck, Ruhum, SmartSek, SooYa, TerrierLover, TomJ, WatchPug, Waze, _Adam, asutorufos, auditor0517, bardamu, c3phas, catchup, cccz, ch13fd357r0y3r, cloudjunky, cmichel, cryptphi, csanuragjain, defsec, fatherOfBlocks, hansfriese, hyh, jayjonah8, joestakey, k, kenta, obtarian, oyc_109, robee, sach1r0, shenwilly, simon135, slywaters, sorrynotsorry, tintin, unforgiven, xiaoming90, zzzitron
699.644 USDC - $699.64
To initialize contract parameters, most contracts employ an init
pattern (rather than a constructor). They are vulnerable to front-running race conditions where an attacker/griefer can front-run (cannot access control because admin roles are not initialized) to initially with their own (malicious) parameters upon detecting (if an event is emitted) which the contract deployer has to redeploy wasting gas and risking other transactions from interacting with the attack unless they are enforced to be atomic with contact deployment via deployment script or factory contracts.
Many init routines (such as DiamondInit.init
) lack an explicit event emission, making it difficult to monitor such scenarios.
Affected source code:
The contract OwnerPausableUpgradeable
is Ownable
and Pausable
, so the owner could resign while the contract is paused, causing a Denial of Service. Owner resignation while paused should be avoided.
Affected source code:
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.
If the owner calls the following methods, it does not verify that the proposed address is valid.
Affected source code:
The pragma version used are:
pragma solidity >=0.6.11; pragma solidity 0.8.14; pragma solidity >=0.7.6;
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
The packages used are out of date, it is good practice to use the latest version of these packages:
"@openzeppelin/contracts": "4.5.0", "@openzeppelin/contracts-upgradeable": "4.5.2",
Check for address(0)
during constructor
, otherwise it could lead to bad initialization, bad implementations, or bad admin changes.
Affected source code without checking address(0)
:
Router who took out the credit
, and router
is not used:
Asserts caller is the router owner (if set) or the router itself
, the router is only checked if there are no owner
:
TODO usually mean something still have to be checked of done. This could lead to vulnerabilities if not verified.
Affected source code:
And this seems more important, because routers can repay any-amount out-of-band using the repayAavePortal
method or by interacting with the aave contracts directly:
encode
instead of encodePacked
for hashigUse of abi.encodePacked in ConnextMessage
is safe, but unnecessary and not recommended. abi.encodePacked
can result in hash collisions when used with two dynamic arguments (string/bytes).
There is also discussion of removing abi.encodePacked
from future versions of Solidity (ethereum/solidity#11593), so using abi.encode
now will ensure compatibility in the future.
keccak256(abi.encodePacked(bytes(_name).length, _name, bytes(_symbol).length, _symbol, _decimals));
:
The methods pause
and unpause
of ProposedOwnableFacet.sol#L253-L261 contract does not check that the contract is already paused or not, it could produce double emits and dApps could be affected.
ERC20
. This is a terrible practice that reduces the readability and auditability of the code.#0 - jakekidd
2022-07-01T22:36:23Z
Outdated compiler lacking examples?
OZERC20 is just an open zeppelin ERC20 implementation.
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xmint, BowTiedWardens, ElKu, Fitraldys, Funen, Kaiziron, Lambda, Metatron, MiloTruck, Randyyy, Ruhum, SmartSek, TomJ, Tomio, UnusualTurtle, Waze, _Adam, apostle0x01, asutorufos, c3phas, catchup, csanuragjain, defsec, fatherOfBlocks, hansfriese, hyh, ignacio, joestakey, k, kaden, nahnah, oyc_109, rfa, robee, sach1r0, simon135, slywaters
85.3344 USDC - $85.33
Use delete
instead of set to default value (false
or 0
)
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 save gas changine the following logic.
Reduce script size changing:
if (_amount == 0 || local == _asset) { return (_amount, local); }
address adopted = s.canonicalToAdopted[id]; if (adopted == _asset || _amount == 0) { return (_amount, adopted); }
Use scientific notation (e.g. 10E18) rather than exponentiation (e.g. 10**18)
10**6
:
Store in a constant 10**uint256(POOL_PRECISION_DECIMALS)
:
The following contracts are never used. It's an excellent idea to take them out to save gas and improve the codding style.
owner
never can be address(0)
, renounceOwnership
require a valid proposed owner:
The method ERC20._setupDecimals
is not used, is initialized like in BridgeToken.sol#L101
Change the type of the argument _assetId
to IERC20 _assetId
:
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:
++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:
Use bool
or uint8
instead of uint256
, this could help to save one storage slots:
The following structs could be optimized moving the position of certains values in order to save slot storages:
struct CallParams { address to; bytes callData; uint32 originDomain; uint32 destinationDomain; address agent; address recovery; address callback; uint256 callbackFee; uint256 relayerFee; bool forceSlow; // Move together to destinationDomain bool receiveLocal; // Move together to destinationDomain uint256 slippageTol; }
Move the bool
types close to address
in
LibConnextStorage.sol#L247
#0 - 0xleastwood
2022-08-16T19:46:29Z
Some good optimisations but also important to point out some invalid ones.
Dead Code - renounceOwnership
requires that _proposed
is the zero address, so this is incorrect.
Delete Optimization and Avoid unused returns - This would typically be optimised out by the Solidity compiler.