Platform: Code4rena
Start Date: 12/07/2022
Pot Size: $35,000 USDC
Total HM: 13
Participants: 78
Period: 3 days
Judge: 0xean
Total Solo HM: 6
Id: 135
League: ETH
Rank: 20/78
Findings: 2
Award: $157.79
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: joestakey
Also found by: 0x1f8b, 0x52, 0xDjango, 0xNazgul, 0xNineDec, 8olidity, Avci, Bahurum, Bnke0x0, Chom, ElKu, Funen, GimelSec, JC, Junnon, Kaiziron, Meera, PaludoX0, Picodes, ReyAdmirado, Sm4rty, Soosh, Waze, _Adam, __141345__, ak1, aysha, benbaessler, bin2chen, c3phas, cccz, cryptphi, csanuragjain, defsec, exd0tpy, fatherOfBlocks, gogo, hake, hansfriese, itsmeSTYJ, jonatascm, kyteg, mektigboy, oyc_109, pashov, rbserver, rishabh, robee, rokinot, sach1r0, sashik_eth, scaraven, simon135, slywaters
131.4909 USDC - $131.49
withdraw
and redeem
function would not work if msg.sender != holder
Both functions in case when msg.sender != holder
checks allowance
amount in wrong way - in case if allowance >= previewAmount
they revert transaction instead of checking for allowance < previewAmount
, this case would be reverted in next line due to check on underflow allowance[holder][msg.sender] -= previewAmount
This lead to inability to use them using "allowance" functionality.
function withdraw(uint256 underlyingAmount, address receiver, address holder) external override returns (uint256 principalAmount){ uint256 previewAmount = this.previewWithdraw(underlyingAmount); if (block.timestamp < maturity) { revert Maturity(maturity); } if (holder == msg.sender) { redeemer.authRedeem(protocol, underlying, maturity, msg.sender, receiver, previewAmount); return previewAmount; } else { uint256 allowed = allowance[holder][msg.sender]; if (allowed >= previewAmount) { revert Approvals(allowed, previewAmount); } allowance[holder][msg.sender] -= previewAmount; redeemer.authRedeem(protocol, underlying, maturity, holder, receiver, previewAmount); return previewAmount; } } function redeem(uint256 principalAmount, address receiver, address holder) external override returns (uint256 underlyingAmount){ if (block.timestamp < maturity) { revert Maturity(maturity); } if (holder == msg.sender) { return redeemer.authRedeem(protocol, underlying, maturity, msg.sender, receiver, principalAmount); } else { uint256 allowed = allowance[holder][msg.sender]; if (allowed >= principalAmount) { revert Approvals(allowed, principalAmount); } allowance[holder][msg.sender] -= principalAmount; return redeemer.authRedeem(protocol, underlying, maturity, holder, receiver, principalAmount); } }
https://github.com/code-423n4/2022-07-swivel/blob/main/Tokens/ZcToken.sol#L98-L143
Recommendation:
Update allowance check to if (allowed < principalAmount) { revert Approvals(allowed, principalAmount); }
Also due to check logic next line could be unchecked
for gas saving:
allowance[holder][msg.sender] -= principalAmount;
Contracts do not emit relevant events after setting sensitive variables. Consider emitting events after sensitive changes take place, to facilitate tracking and notify off-chain clients following the contractโs activity in following functions:
/// @param a Address of a new admin function setAdmin(address a) external authorized(admin) returns (bool) { missing event admin = a; return true; } /// @param m Address of the deployed marketPlace contract /// @notice We only allow this to be set once function setMarketPlace(address m) external authorized(admin) returns (bool) { missing event if (marketPlace != address(0)) { revert Exception(33, 0, 0, marketPlace, address(0)); } marketPlace = m; return true; }
https://github.com/code-423n4/2022-07-swivel/blob/main/Creator/Creator.sol#L47-L57
/// @param s Address of the deployed swivel contract /// @notice We only allow this to be set once function setSwivel(address s) external authorized(admin) returns (bool) { if (swivel != address(0)) { revert Exception(20, 0, 0, swivel, address(0)); } swivel = s; return true; } /// @param a Address of a new admin function setAdmin(address a) external authorized(admin) returns (bool) { admin = a; return true; }
https://github.com/code-423n4/2022-07-swivel/blob/main/Marketplace/MarketPlace.sol#L45-L56
/// @notice Matures the vault /// @param c The current cToken exchange rate function matureVault(uint256 c) external authorized(admin) returns (bool) { maturityRate = c; return true; }
https://github.com/code-423n4/2022-07-swivel/blob/main/VaultTracker/VaultTracker.sol#L143-L146
/// @param a Address of a new admin function setAdmin(address a) external authorized(admin) returns (bool) { admin = a; return true; }
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L428-L432
Few functions fails to perform input validation on arrays to verify the lengths match. A mismatch could lead to an exception or undefined behavior. https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L82-L104
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L244-L273
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L407-L423
Swivel/Swivel.sol:33 address public aaveAddr; // TODO immutable? Swivel/Swivel.sol:120 // TODO cheaper to assign amount here or keep the ADD? Swivel/Swivel.sol:157 // TODO assign amount or keep the ADD? Swivel/Swivel.sol:192 // TODO assign amount or keep the ADD? Swivel/Swivel.sol:221 // TODO assign amount or keep ADD? Swivel/Swivel.sol:317 // TODO assign amount or keep the ADD? Swivel/Swivel.sol:347 // TODO assign amount or keep the ADD? Swivel/Swivel.sol:382 // TODO assign amount or keep the ADD? Swivel/Swivel.sol:708 if (p == uint8(Protocols.Compound)) { // TODO is Rari a drop in here? Swivel/Swivel.sol:741 if (p == uint8(Protocols.Compound)) { // TODO is Rari a drop in here? Swivel/Swivel.sol:748 // TODO explain the withdraw args Swivel/Swivel.sol:752 // TODO explain the 0
Swivel/Swivel.sol:36 /// @dev holds the fee demoninators for [zcTokenInitiate, zcTokenExit, vaultInitiate, vaultExit]
Swivel/Swivel.sol:677 // NOTE: for swivel reddem there is no transfer out as there is in redeemVaultInterest
Swivel/Swivel.sol:682 /// @notice Varifies the validity of an order and it's signature.
Swivel/Swivel.sol:747 // Aave v2 docs state that withraw returns uint256
Function has name authRedeemZcToken
:
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L620
While interface decleres it authRedeem
https://github.com/code-423n4/2022-07-swivel/blob/main/Marketplace/Interfaces.sol#L52
#0 - robrobbins
2022-08-25T23:01:41Z
the zctoken comparisons were addressed in another ticket
๐ Selected for report: joestakey
Also found by: 0x040, 0x1f8b, 0xDjango, 0xNazgul, 0xsam, Avci, Aymen0909, Bnke0x0, CRYP70, ElKu, Fitraldys, Funen, JC, Kaiziron, MadWookie, Meera, ReyAdmirado, Sm4rty, Soosh, TomJ, Waze, _Adam, __141345__, ajtra, benbaessler, c3phas, csanuragjain, durianSausage, exd0tpy, fatherOfBlocks, hake, ignacio, karanctf, kyteg, m_Rassska, oyc_109, rbserver, robee, rokinot, samruna, sashik_eth, simon135, slywaters
26.3021 USDC - $26.30
Since reading from memory
is much cheaper than reading from storage
, state variables that are called more than 1 SLOAD inside the function should be cached.
Marketplace/MarketPlace.sol:77 (address zct, address tracker) = ICreator(creator).create(p, underAddr, m, c, swivel, n, s, IErc20(underAddr).decimals()) ; // swivel 2nd SLOAD Swivel/Swivel.sol:502 if (block.timestamp < feeChange) { revert Exception(17, block.timestamp, feeChange, address(0), address(0)); } // feeChange 2nd SLOAD
It's possible to avoid storage access and save gas using immutable
keyword for the following variables:
Swivel/Swivel.sol:33 address public aaveAddr;
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L33
unchecked
block can be used for gas efficiency of the expression that can't overflow/underflowL363 could be unchecked
since due to L362 fee <= premiumFilled
Swivel/Swivel.sol:362 uint256 fee = premiumFilled / feenominators[3]; Swivel/Swivel.sol:363 Safe.transfer(uToken, msg.sender, premiumFilled - fee);
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L363
Lines with adding block.timestamp
value to constant value could be unchecked
since their overflow would appear only in the extremely far future:
Swivel/Swivel.sol:437 uint256 when = block.timestamp + HOLD; Swivel/Swivel.sol:474 uint256 when = block.timestamp + HOLD; Swivel/Swivel.sol:523 uint256 when = block.timestamp + HOLD;
Prefix increment is cheaper than postfix increment. Consider using ++i in next lines:
Swivel/Swivel.sol:100 unchecked {i++;} Swivel/Swivel.sol:269 unchecked {i++;} Swivel/Swivel.sol:418 i++; Swivel/Swivel.sol:511 x++; Swivel/Swivel.sol:564 i++;
Line 121 could be filled[hash] = amount
without repeated computation from line 116:
Swivel/Swivel.sol:116 uint256 amount = a + filled[hash]; ... Swivel/Swivel.sol:121 filled[hash] += a;
https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L116-L121 Same here: https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L158 https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L193 https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L222 https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L287 https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L318 https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L348 https://github.com/code-423n4/2022-07-swivel/blob/main/Swivel/Swivel.sol#L383
#0 - robrobbins
2022-08-31T19:19:00Z
some addressed in other tickets
rest dupes