Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $50,000 USDC
Total HM: 31
Participants: 99
Period: 5 days
Judges: moose-code, JasoonS, denhampreen
Total Solo HM: 17
Id: 139
League: ETH
Rank: 3/99
Findings: 5
Award: $3,436.45
π Selected for report: 3
π Solo Findings: 1
π Selected for report: 0x1f8b
Also found by: BowTiedWardens, Lambda, StErMi, berndartmueller, csanuragjain, neumo, rfa
1817.5513 USDC - $1,817.55
https://github.com/code-423n4/2022-06-yieldy/blob/34774d3f5e9275978621fd20af4fe466d195a88b/src/contracts/BatchRequests.sol#L93 https://github.com/code-423n4/2022-06-yieldy/blob/34774d3f5e9275978621fd20af4fe466d195a88b/src/contracts/BatchRequests.sol#L57 https://github.com/code-423n4/2022-06-yieldy/blob/34774d3f5e9275978621fd20af4fe466d195a88b/src/contracts/BatchRequests.sol#L37
The BatchRequests.removeAddress
logic is wrong and it will produce a denial of service.
Removing the element from the array is done using the delete
statement, but this is not the proper way to remove an entry from an array, it will just set that position to address(0)
.
Append dummy data:
addAddress('0x0000000000000000000000000000000000000001')
addAddress('0x0000000000000000000000000000000000000002')
addAddress('0x0000000000000000000000000000000000000003')
getAddresses()
=> address[]: 0x0000000000000000000000000000000000000001,0x0000000000000000000000000000000000000002,0x0000000000000000000000000000000000000003
Remove address:
removeAddress(0x0000000000000000000000000000000000000002)
(or 0x0000000000000000000000000000000000000003
)getAddresses()
=> address[]: 0x0000000000000000000000000000000000000001,0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000003
Service is denied because it will try to call canBatchContracts
to address(0)
.
pop
and move the last element to the removed entry position.#0 - toshiSat
2022-06-28T16:32:06Z
duplicate #280
#1 - 0x1f8b
2022-07-01T16:14:02Z
@toshiSat
duplicate https://github.com/code-423n4/2022-06-yieldy-findings/issues/280
Is not the same issue, here I describe a wrong logic during removeAddress
#2 - toshiSat
2022-07-01T17:51:54Z
@toshiSat
duplicate #280
Is not the same issue, here I describe a wrong logic during
removeAddress
Sorry! lot's of issues
duplicate #289
#3 - JasoonS
2022-07-29T13:14:21Z
Agree this is high, if the team (owner) didn't know this they could cause some issues for sure.
327.1592 USDC - $327.16
The security of the Yieldy
contract is delegated to the compiler used.
The allowance
of an account does not have to reflect the real balance of an account, however in the transferFrom
method, it is the value that is checked in order to verify that the user has enough balance to make the transfer.
function transferFrom( address _from, address _to, uint256 _value ) public override returns (bool) { require(_allowances[_from][msg.sender] >= _value, "Allowance too low");
However, the real balance of the Yieldy
contract is based on the calculation made by the creditsForTokenBalance
method, so an underflow could be made in the subtraction of the balance of the from
account.
uint256 creditAmount = creditsForTokenBalance(_value); creditBalances[_from] = creditBalances[_from] - creditAmount; creditBalances[_to] = creditBalances[_to] + creditAmount; emit Transfer(_from, _to, _value);
This means that the security of the contract is delegated to the checks added by the compiler depending on the pragma used, it must be taken into account that these checks may appear and disappear in future versions of the compiler, so they must be checked at the level of smart contracts.
Affected source code:
creditAmount
balance.#0 - toshiSat
2022-07-29T20:41:19Z
Looking into this, the balance isn't calculated through the creditsForTokenBalance
method, it's calculated through the balanceOf
method, which in this case the functionality is correct. We aren't transferring credits, we are transferring the value and adding to the credits. Allowance is for value amounts, not credits, also balance can only go up against credits, so if the balance is valid then credits are inherently valid too. I'm unsure of what to label this as, because we do need to check to see if the user has the correct balance. I feel like this issue is partially correct
#1 - toshiSat
2022-07-29T20:45:44Z
π Selected for report: 0x1f8b
1211.7009 USDC - $1,211.70
Using the burn()
function of Yieldy
, an address with MINTER_BURNER_ROLE
can burn an arbitrary amount of tokens from any address.
We believe this is unnecessary and poses a serious centralization risk.
A malicious or compromised MINTER_BURNER_ROLE
address can take advantage of this.
Consider removing the MINTER_BURNER_ROLE
and change burn()
function to:
function burn(uint256 _amount) external override { _burn(_msgSender(), _amount); }
#0 - toshiSat
2022-07-27T22:35:18Z
There's tons of centralization risks already, this is acknowledged, but for yieldies to work, there needs to be a trusted party
#1 - JasoonS
2022-08-29T16:51:36Z
Leaving as medium - the code can be upgraded but the code is being assessed as is.
π Selected for report: IllIllI
Also found by: 0x1337, 0x1f8b, 0x29A, 0x52, 0xDjango, 0xNazgul, 0xNineDec, 0xc0ffEE, 0xf15ers, 0xmint, Bnke0x0, BowTiedWardens, Chom, ElKu, FudgyDRS, Funen, GalloDaSballo, GimelSec, JC, Kaiziron, Lambda, Limbooo, Metatron, MiloTruck, Noah3o6, Picodes, PumpkingWok, PwnedNoMore, Sm4rty, StErMi, TomJ, TrungOre, UnusualTurtle, Waze, _Adam, aga7hokakological, ak1, antonttc, berndartmueller, cccz, cryptphi, csanuragjain, defsec, delfin454000, dipp, elprofesor, exd0tpy, fatherOfBlocks, hake, hansfriese, hubble, joestakey, kenta, ladboy233, mics, oyc_109, pashov, pedr02b2, reassor, robee, samruna, scaraven, shung, sikorico, simon135, sseefried, tchkvsky, unforgiven, zzzitron
53.3348 USDC - $53.33
https://github.com/code-423n4/2022-06-yieldy/blob/8400e637d9259b7917bde259a5a2fbbeb5946d45/src/contracts/Yieldy.sol#L38 https://github.com/code-423n4/2022-06-yieldy/blob/8400e637d9259b7917bde259a5a2fbbeb5946d45/src/contracts/Yieldy.sol#L61-L62
A deprecated method is used in Yieldy
contract.
In Yieldy
contract the method _setupRole
is used, and and it is explicitly marked as deprecated by OpenZeppelin.
- NOTE: This function is deprecated in favor of {_grantRole}.
Affected source code:
_setupRole
must be changed to _grantRole
.#0 - JasoonS
2022-07-26T13:58:12Z
Low severity - agreed
π Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xmint, 8olidity, ACai, Bnke0x0, Chom, ElKu, Fabble, Fitraldys, FudgyDRS, Funen, GalloDaSballo, GimelSec, IllIllI, JC, Kaiziron, Lambda, Limbooo, MiloTruck, Noah3o6, Nyamcil, Picodes, PwnedNoMore, Randyyy, RedOneN, Sm4rty, StErMi, TomJ, Tomio, TrungOre, UnusualTurtle, Waze, _Adam, aga7hokakological, ajtra, antonttc, asutorufos, bardamu, c3phas, defsec, delfin454000, exd0tpy, fatherOfBlocks, hansfriese, ignacio, joestakey, kenta, ladboy233, m_Rassska, mics, minhquanym, oyc_109, pashov, reassor, robee, s3cunda, sach1r0, saian, sashik_eth, scaraven, sikorico, simon135, slywaters
26.7051 USDC - $26.71
Use scientific notation (e.g. 10e17) rather than exponentiation (e.g. 10**18)
Change 10**3
to 10e2
:
It's possible to save storage slots reorganizing the following contracts:
Move isReserveEnabled
close to stakingContract
(or other address type) in LiquidityReserveStorage.sol#L11
Move decimal
close to stakingContract
in YieldyStorage.sol#L23
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:
Counter
to only increase one variableThe Counter
contract is only used to increase a variable and involves unnecessary cost overruns.
Affected source code:
unckecked
keywordIt's possible to save gas using the unckecked
keyword. This will avoid the required checks to ensure that the variable won't overflow.
Reference:
Affected source code:
IStaking
in BatchRequests.sol#L9