Platform: Code4rena
Start Date: 16/01/2024
Pot Size: $80,000 USDC
Total HM: 37
Participants: 178
Period: 14 days
Judge: Picodes
Total Solo HM: 4
Id: 320
League: ETH
Rank: 61/178
Findings: 4
Award: $163.11
🌟 Selected for report: 0
🚀 Solo Findings: 0
53.4926 USDC - $53.49
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L317 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L385 https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L278
Altering the quorum for a proposal could result in unexpected outcomes since, for all active proposals, the current quorum is considered instead of the quorum that was present at the start of the proposal.
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L278
The finalizeBallot()
function is called to execute the proposal, checking if the minimum time and quorum for the proposal are met using proposals.canFinalizeBallot(ballotID)
.
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L385
canFinalizeBallot()
examines the ballotMinimumNedTime
from the ballot storage variable. For quorum, it assumes that the quorum remains the same as it was at the time of proposing the proposal. However, the current quorum depends on various parameters.
function requiredQuorumForBallotType( BallotType ballotType ) public view returns (uint256 requiredQuorum) { // The quorum will be specified as a percentage of the total amount of SALT staked uint256 totalStaked = staking.totalShares( PoolUtils.STAKED_SALT ); require( totalStaked != 0, "SALT staked cannot be zero to determine quorum" ); if ( ballotType == BallotType.PARAMETER ) requiredQuorum = ( 1 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 ); else if ( ( ballotType == BallotType.WHITELIST_TOKEN ) || ( ballotType == BallotType.UNWHITELIST_TOKEN ) ) requiredQuorum = ( 2 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 ); else // All other ballot types require 3x multiple of the baseQuorum requiredQuorum = ( 3 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 ); // Make sure that the requiredQuorum is at least 0.50% of the total SALT supply. // Circulating supply after the first 45 days of emissions will be about 3 million - so this would require about 16% of the circulating // SALT to be staked and voting to pass a proposal (including whitelisting) 45 days after deployment.. uint256 totalSupply = ERC20(address(exchangeConfig.salt())).totalSupply();//@audit increase total supply will increase the quorum uint256 minimumQuorum = totalSupply * 5 / 1000; if ( requiredQuorum < minimumQuorum ) requiredQuorum = minimumQuorum; }
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L317
The issue arises as the quorum is global for all proposals, leading to the execution of proposals with incorrect quorum values
Proposer could wait to decrease/increase to call finalize ballot depending upon his/her pro and cons
Manual
implement tracking of quorum for proposals from the time of creation. During execution, compare the stored quorum with the current quorum to ensure accurate execution of proposals.
Context
#0 - c4-judge
2024-02-02T22:20:41Z
Picodes marked the issue as primary issue
#1 - c4-sponsor
2024-02-12T21:10:34Z
othernet-global (sponsor) disputed
#2 - othernet-global
2024-02-12T21:11:03Z
Yes, this is acceptable as part of the reason to lower quorum would be to push through previous proposals.
#3 - c4-judge
2024-02-20T11:31:07Z
Picodes changed the severity to QA (Quality Assurance)
#4 - Picodes
2024-02-20T11:31:39Z
Downgrading to QA as although it is risky and requires careful checks when lowering the quorum this is a suitable design.
#5 - Tri-pathi
2024-02-23T20:51:39Z
Hey @Picodes
Thanks for smooth judging.
Sponsor comment- Yes, this is acceptable as part of the reason to lower quorum would be to push through previous proposals
what about the proposals which were malicious/bad for protocol and didn't able to get required votes during proposed time. Lowering quorum won't push only indented proposals it will push above malicious proposals too and it is way risky than it seems. I believe a core property is breaking here and hence qualifies for Medium severity{ even if won't fix tag from the sponsor }.
PS: More than welcome to whatever decision you take, but please have a second look at the impact
#6 - Picodes
2024-02-26T14:07:58Z
Thanks for your comment. This is a duplicate of https://github.com/code-423n4/2024-01-salty-findings/issues/764. See my arguments there.
#7 - Picodes
2024-02-28T10:57:37Z
@Tri-pathi following the discussion on #764 I'll upgrade this issue as well.
#8 - c4-judge
2024-02-28T10:58:20Z
This previously downgraded issue has been upgraded by Picodes
#9 - c4-judge
2024-02-28T10:58:36Z
Picodes marked the issue as duplicate of #362
#10 - c4-judge
2024-02-28T10:58:39Z
Picodes marked the issue as satisfactory
44.44 USDC - $44.44
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L14 https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L102
The protocol may experience a reduction in liquidity/shares and suboptimal trades due to the absence of slippage protection and deadline checks
https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L316
The DAO invokes formPOL
to create SALT/USD or USDS/DAI protocol-owned liquidity but does not specify parameters for slippages/minimum liquidity and deadline.
This function then calls
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L146
collateralAndLiquidity.depositLiquidityAndIncreaseShare()
with minLiquidityReceived=0
and deadline=block.timestamp
i.e neither slippage nor deadline has been implemented properly
Moreover, within the same function, the deadline
parameter is discarded before any checks for calling internal _depositLiquidityAndIncreaseShare()
. In other words, even if a front-end user attempts to provide a deadline, it won't be taken into account.
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L102
Upon further analysis, it becomes evident that it adds/withdraw liquidity to/from the pool without any slippage and deadline checks, leading to very unfavorable trades
Manual
Implement slippage and deadline checks appropriately throughout the codebase.
Token-Transfer
#0 - c4-judge
2024-02-02T10:47:22Z
Picodes marked the issue as duplicate of #224
#1 - c4-judge
2024-02-19T14:12:19Z
Picodes changed the severity to 2 (Med Risk)
#2 - c4-judge
2024-02-21T15:30:37Z
Picodes marked the issue as satisfactory
53.4926 USDC - $53.49
https://github.com/code-423n4/2024-01-salty/blob/main/src/price_feed/CoreChainlinkFeed.sol#L21
Reserve ratios and pool constraints may break if the WBTC bridge becomes compromised, and WBTC undergoes depegging
The chainlink BTC/USD oracle is utilized to price WBTC [https://github.com/code-423n4/2024-01-salty/blob/main/src/price_feed/CoreChainlinkFeed.sol#L21] . WBTC, being a bridged asset, could depeg if the bridge is compromised or fails, causing it to no longer be equivalent to BTC. This scenario would result in all swap functions (essentially the entire protocol) being active against an asset that is now effectively worthless.The protocol continues to value WBTC via BTC/USD, leading to several potential malicious actions, especially since many invariants depend on the WBTC/ETH pool
As the protocol operates smoothly with at least two functioning price feeds,the CoreUniswapFeed
UniV3 TWAP safeguards against such asset depegging but other two CoreSaltyFeed
and CoreChainlinkFeed
will have no effect and hence protocol will keep functioning even in such case
Manual
Conduct a thorough analysis and implement a robust solution for determining the price of WBTC.
Oracle
#0 - c4-judge
2024-02-01T16:35:39Z
Picodes marked the issue as duplicate of #632
#1 - c4-judge
2024-02-20T15:51:24Z
Picodes marked the issue as satisfactory
🌟 Selected for report: juancito
Also found by: 0x3b, 0xBinChook, 0xCiphky, 0xHelium, 0xMango, 0xOmer, 0xRobocop, 0xSmartContractSamurai, 0xWaitress, 0xbepresent, 0xpiken, 7ashraf, Arz, Audinarey, Banditx0x, Bauchibred, Draiakoo, IceBear, J4X, Jorgect, Kaysoft, KingNFT, Rhaydden, Rolezn, The-Seraphs, Tigerfrake, Topmark, Tripathi, Udsen, ZanyBonzy, a3yip6, b0g0, chaduke, codeslide, csanuragjain, dharma09, djxploit, eta, ether_sky, grearlake, inzinko, jasonxiale, jesjupyter, josephdara, lanrebayode77, linmiaomiao, lsaudit, niroh, oakcobalt, peanuts, pina, sivanesh_808, slvDev, t0x1c, vnavascues, wangxx2026
11.6897 USDC - $11.69
https://github.com/code-423n4/2024-01-salty/blob/main/src/launch/BootstrapBallot.sol#L69
f the protocol fails to receive enough votes during the initial proposal, all funds sent to the InitialDistribution will become stuck (1000000 ether)
https://github.com/code-423n4/2024-01-salty/blob/main/src/launch/BootstrapBallot.sol#L69
The finalizeBallot()
function is called for the initial distribution, airdrop, and starting the exchange at the launch time. However, if startExchangeYes <= startExchangeNo
, this condition leads to ballotFinalized = true
, preventing further calls. There is currently no way to retrieve the funds, as the protocol assumes it will receive startExchangeYes > startExchangeNo
. A malicious competitor or attacker could exploit this by funding the attack or employing other methods to ensure the condition holds
The main issue is that the protocol cannot propose for a vote a second time. If it fails to get enough startExchange
votes the first time, it will lose all the funds
Manual
Implement a better approach that allows the protocol to repropose in case it doesn't receive enough votes initially:
Provide a period for protocol teams to reevaluate and advertise the protocol after the first failed proposal
Implement measures to make reproposing difficult for attackers by increasing the cost of such attacks
DoS
#0 - c4-judge
2024-02-02T12:04:28Z
Picodes marked the issue as duplicate of #606
#1 - c4-judge
2024-02-19T11:08:18Z
Picodes changed the severity to QA (Quality Assurance)
#2 - c4-judge
2024-02-21T17:00:56Z
Picodes marked the issue as grade-b