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: 17/78
Findings: 2
Award: $216.24
๐ 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
176.7351 USDC - $176.74
File: Creator.sol line 39
Missing @return
/// @param d Decimals of the new market zcToken function create ( uint8 p, address u, uint256 m, address c, address sw, string memory n, string memory s, uint8 d ) external authorized(marketPlace) returns (address, address) {
File: Creator.sol line 46-50
Missing @return
/// @param a Address of a new admin function setAdmin(address a) external authorized(admin) returns (bool) { admin = a; return true; }
Contracts should be deployed with the same compiler version and flags that they have been tested the most with. Locking the pragma helps ensure that contracts do not accidentally get deployed using, for example, the latest compiler which may have higher risks of undiscovered bugs. Contracts may also be deployed by others and the pragma indicates the compiler version intended by the original authors.
File: ZcToken.sol line 2
pragma solidity ^0.8.4;
Using both named returns and a return statement isnโt necessary in a function.To improve code quality, consider using only one of those.
File: MarketPlace.sol line 148-168
function authRedeem(uint8 p, address u, uint256 m, address f, address t, uint256 a) public authorized(markets[p][u][m].zcToken) returns (uint256 underlyingAmount) { Market memory market = markets[p][u][m]; // if the market has not matured, mature it... if (market.maturityRate == 0) { if (!matureMarket(p, u, m)) { revert Exception(30, 0, 0, address(0), address(0)); } if (!IZcToken(market.zcToken).burn(f, a)) { revert Exception(29, 0, 0, address(0), address(0)); } ISwivel(swivel).authRedeem(p, u, market.cTokenAddr, t, a); return (a); } else { if (!IZcToken(market.zcToken).burn(f, a)) { revert Exception(29, 0, 0, address(0), address(0)); } uint256 amount = calculateReturn(p, u, m, a); ISwivel(swivel).authRedeem(p, u, market.cTokenAddr, t, amount); return (amount); } }
File: ZcToken.sol line 43-48
function convertToUnderlying(uint256 principalAmount) external override view returns (uint256 underlyingAmount){ if (block.timestamp < maturity) { return 0; } return (principalAmount * IRedeemer(redeemer).getExchangeRate(protocol, cToken) / IRedeemer(redeemer).markets(protocol, underlying, maturity).maturityRate); }
File: ZcToken.sol line 52-57
function convertToPrincipal(uint256 underlyingAmount) external override view returns (uint256 principalAmount){ if (block.timestamp < maturity) { return 0; } return (underlyingAmount * IRedeemer(redeemer).markets(protocol, underlying, maturity).maturityRate / IRedeemer(redeemer).getExchangeRate(protocol, cToken)); }
File: ZcToken.sol line 61-66
function maxRedeem(address owner) external override view returns (uint256 maxPrincipalAmount){ if (block.timestamp < maturity) { return 0; } return (balanceOf[owner]); }
File: ZcToken.sol line 70-75 File: ZcToken.sol line 79-84 File: ZcToken.sol line 88-93
File: Swivel.sol line 33
address public aaveAddr; // TODO immutable?
File: Swivel.sol line 120
// TODO cheaper to assign amount here or keep the ADD?
File: Swivel.sol line 157
// TODO assign amount or keep the ADD?
File: Swivel.sol line 192
// TODO assign amount or keep the ADD?
File: Swivel.sol line 221
// TODO assign amount or keep ADD?
File: Swivel.sol line 707-708
// TODO as stated elsewhere, we may choose to simply return true in all and not attempt to measure against any expected return if (p == uint8(Protocols.Compound)) { // TODO is Rari a drop in here?
File: Swivel.sol line 286 File: Swivel.sol line 317 File: Swivel.sol line 347 File: Swivel.sol line 716 File: Swivel.sol line 721 File: Swivel.sol line 748
File: VaultTracker.sol line 35
cTokenAddr = c;
#0 - robrobbins
2022-08-12T22:49:50Z
open todos are all closed as per #200
#1 - robrobbins
2022-08-12T22:50:39Z
it's not required to have ///@return in natspec as both the methods and the interfaces explicitly state them
๐ 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
39.4957 USDC - $39.50
File: Swivel.sol line 33
address public aaveAddr; // TODO immutable?
The above address is set in the constructor and never set again in the contract
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isnโt possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block see resource
File:ZcToken.sol line 115
allowance[holder][msg.sender] -= previewAmount;
The above line cannot underflow due to the check on line 112 which ensures that allowance[holder][msg.sender]
is greater than previewAmount
before performing the arithmetic operation
The above can be modified to:
unchecked { allowance[holder][msg.sender] -= previewAmount; }
File:ZcToken.sol line 134
allowance[holder][msg.sender] -= principalAmount;
The above line cannot underflow due to the check on line 133 which ensures that allowance[holder][msg.sender]
is greater than principalAmount
before performing the arithmetic operation
The code can be optimized by minimizing the number of SLOADs. SLOADs are expensive 100 gas compared to MLOADs/MSTOREs(3gas) Storage value should get cached in memory
NB: Some functions have been truncated where necessary to just show affected parts of the code
File: VaultTracker.sol line 59,60
function addNotional(address o, uint256 a) external authorized(admin) returns (bool) { uint256 exchangeRate = Compounding.exchangeRate(protocol, cTokenAddr); Vault memory vlt = vaults[o]; if (vlt.notional > 0) { uint256 yield; // if market has matured, calculate marginal interest between the maturity rate and previous position exchange rate // otherwise, calculate marginal exchange rate between current and previous exchange rate. if (maturityRate > 0) { // Calculate marginal interest yield = ((maturityRate * 1e26) / vlt.exchangeRate) - 1e26; } else { yield = ((exchangeRate * 1e26) / vlt.exchangeRate) - 1e26; } return true; }
In the above function maturityRate should be cached in memory to minimize the number of SLOADs
SLOAD 1: line 59 SLOAD 2: line 60
File: VaultTracker.sol line 93,94
function removeNotional(address o, uint256 a) external authorized(admin) returns (bool) { // if market has matured, calculate marginal interest between the maturity rate and previous position exchange rate // otherwise, calculate marginal exchange rate between current and previous exchange rate. uint256 yield; if (maturityRate > 0) { // Calculate marginal interest yield = ((maturityRate * 1e26) / vlt.exchangeRate) - 1e26; } else { // calculate marginal interest yield = ((exchangeRate * 1e26) / vlt.exchangeRate) - 1e26; } return true; }
In the above function maturityRate should be cached in memory to minimize the number of SLOADs
SLOAD 1: line 93 SLOAD 2: line 94
File: VaultTracker.sol line 123,124
function redeemInterest(address o) external authorized(admin) returns (uint256) { // if market has matured, calculate marginal interest between the maturity rate and previous position exchange rate // otherwise, calculate marginal exchange rate between current and previous exchange rate. uint256 yield; if (maturityRate > 0) { // Calculate marginal interest yield = ((maturityRate * 1e26) / vlt.exchangeRate) - 1e26; } else { // calculate marginal interest yield = ((exchangeRate * 1e26) / vlt.exchangeRate) - 1e26; } // return adds marginal interest to previously accrued redeemable interest return (redeemable + interest); }
In the above function maturityRate should be cached in memory to minimize the number of SLOADs
SLOAD 1: line 123 SLOAD 2: line 124
File: VaultTracker.sol line 165,167,184,186
function transferNotionalFrom(address f, address t, uint256 a) external authorized(admin) returns (bool) { if (f == t) { revert Exception(32, 0, 0, f, t); } uint256 yield; if (maturityRate > 0) { // calculate marginal interest yield = ((maturityRate * 1e26) / from.exchangeRate) - 1e26; } else { yield = ((exchangeRate * 1e26) / from.exchangeRate) - 1e26; } ... vaults[f] = from; ... // transfer notional to address "t", calculate interest if necessary if (to.notional > 0) { // if market has matured, calculate marginal interest between the maturity rate and previous position exchange rate // otherwise, calculate marginal exchange rate between current and previous exchange rate. if (maturityRate > 0) { // calculate marginal interest yield = ((maturityRate * 1e26) / to.exchangeRate) - 1e26; } else { yield = ((exchangeRate * 1e26) / to.exchangeRate) - 1e26; } return true; }
In the above function maturityRate should be cached in memory to minimize the number of SLOADs(4 SLOADS)
SLOAD 1: line 165 SLOAD 2: line 167 SLOAD 3: line 184 SLOAD 4: line 186
File: VaultTracker.sol line 222,224
function transferNotionalFee(address f, uint256 a) external authorized(admin) returns(bool) { Vault memory oVault = vaults[f]; uint256 yield; if (sVault.exchangeRate != exchangeRate) { // if market has matured, calculate marginal interest between the maturity rate and previous position exchange rate // otherwise, calculate marginal exchange rate between current and previous exchange rate. if (maturityRate > 0) { // calculate marginal interest yield = ((maturityRate * 1e26) / sVault.exchangeRate) - 1e26; } else { yield = ((exchangeRate * 1e26) / sVault.exchangeRate) - 1e26; } return true; }
In the above function maturityRate should be cached in memory to minimize the number of SLOADs(4 SLOADS)
SLOAD 1: line 222 SLOAD 2: line 224
File: MarketPlace.sol line 180,188
function redeemZcToken(uint8 p, address u, uint256 m, address t, uint256 a) external authorized(swivel) unpaused(p) returns (uint256) { Market memory market = markets[p][u][m]; // if the market has not matured, mature it and redeem exactly the amount if (market.maturityRate == 0) { if (!matureMarket(p, u, m)) { revert Exception(30, 0, 0, address(0), address(0)); } } if (!IZcToken(market.zcToken).burn(t, a)) { revert Exception(29, 0, 0, address(0), address(0)); } emit RedeemZcToken(p, u, m, t, a); if (market.maturityRate == 0) { return a; } else { // if the market was already mature the return should include the amount + marginal floating interest generated on Compound since maturity return calculateReturn(p, u, m, a); } }
In the above function maturityRate should be cached in memory to minimize the number of SLOADs
SLOAD 1: line 180 SLOAD 2: line 188
File: ZcToken.sol line 47
function convertToUnderlying(uint256 principalAmount) external override view returns (uint256 underlyingAmount){ if (block.timestamp < maturity) { return 0; } return (principalAmount * IRedeemer(redeemer).getExchangeRate(protocol, cToken) / IRedeemer(redeemer).markets(protocol, underlying, maturity).maturityRate); }
In the above function redeemer should be cached in memory to minimize the number of SLOADs
It is being read twice from memory in the return statement
File: ZcToken.sol line 56
return (underlyingAmount * IRedeemer(redeemer).markets(protocol, underlying, maturity).maturityRate / IRedeemer(redeemer).getExchangeRate(protocol, cToken));
++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
Instances include:
File: Swivel.sol line 100
unchecked {i++;}
The above should be modified to:
unchecked {++i;}
Other instances File: Swivel.sol line 269
unchecked {i++;}
File: Swivel.sol line 417-419
unchecked { i++; }
File: Swivel.sol line 510-512
unchecked { x++; }
File: Swivel.sol line 563-565
unchecked { i++; }
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.
File: Swivel.sol line 25
string constant public NAME = 'Swivel Finance';
File: Swivel.sol line 26
string constant public VERSION = '3.0.0';
File: Swivel.sol line 27
uint256 constant public HOLD = 3 days;
File: Swivel.sol line 35
uint16 constant public MIN_FEENOMINATOR = 33;
There are several occurrences of literal values with unexplained meaning .Literal values in the codebase without an explained meaning make the code harder to read, understand and maintain, thus hindering the experience of developers, auditors and external contributors alike.
Developers should define a constant variable for every magic value used , giving it a clear and self-explanatory name.
File: VaultTracker.sol line 60 1e26
yield = ((maturityRate * 1e26) / vlt.exchangeRate) - 1e26;
File: VaultTracker.sol line 62 1e26
yield = ((exchangeRate * 1e26) / vlt.exchangeRate) - 1e26;
File: VaultTracker.sol line 65 1e26
uint256 interest = (yield * vlt.notional) / 1e26;
Other instances File: VaultTracker.sol line 94 File: VaultTracker.sol line 97 File: VaultTracker.sol line 100 File: VaultTracker.sol line 124 File: VaultTracker.sol line 127 File: VaultTracker.sol line 130
#0 - robrobbins
2022-08-22T22:49:50Z
resolved elsewhere or wontfix