Platform: Code4rena
Start Date: 12/04/2023
Pot Size: $60,500 USDC
Total HM: 21
Participants: 199
Period: 7 days
Judge: hansfriese
Total Solo HM: 5
Id: 231
League: ETH
Rank: 25/199
Findings: 2
Award: $306.44
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: MiloTruck
Also found by: DedOhWale, giovannidisiena, yixxas
283.8353 USDC - $283.84
https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L241-L255 https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L266-L270
Initial minter can steal from the next investor. The amount stolen is based on how much the next investor chooses to invest.
In observing the onTokenTransfer()
function, we notice that if equity <= amount
, then only 1000 shares will be minted to from
address no matter the amount
deposited. This is used to mint 1000 FPS for the initial deposit, otherwise calculateSharesInternal()
is used to calculate shares.
function onTokenTransfer(address from, uint256 amount, bytes calldata) external returns (bool) { require(msg.sender == address(zchf), "caller must be zchf"); uint256 equity = zchf.equity(); require(equity >= MINIMUM_EQUITY, "insuf equity"); // ensures that the initial deposit is at least 1000 ZCHF // Assign 1000 FPS for the initial deposit, calculate the amount otherwise uint256 shares = equity <= amount ? 1000 * ONE_DEC18 : calculateSharesInternal(equity - amount, amount); _mint(from, shares); emit Trade(msg.sender, int(shares), amount, price()); // limit the total supply to a reasonable amount to guard against overflows with price and vote calculations // the 128 bits are 68 bits for magnitude and 60 bits for precision, as calculated in an above comment require(totalSupply() < 2**128, "total supply exceeded"); return true; }
calculateSharesInternal
sets newTotalShares
to 1000e18 if totalShares < 1000
. This is irrespective of amount of investment
.
function calculateSharesInternal(uint256 capitalBefore, uint256 investment) internal view returns (uint256) { uint256 totalShares = totalSupply(); uint256 newTotalShares = totalShares < 1000 * ONE_DEC18 ? 1000 * ONE_DEC18 : _mulD18(totalShares, _cubicRoot(_divD18(capitalBefore + investment, capitalBefore))); return newTotalShares - totalShares; }
A malicious first depositor can steal from the next investor due to this.
Steps of attack:
redeem()
to redeem 1 share. One condition here is that sufficient blocks must have passed such that redemption is possible.Manual Review
Consider disallowing redemption of shares such that totalSupply() < 1000
.
#0 - c4-pre-sort
2023-04-21T22:03:20Z
0xA5DF marked the issue as primary issue
#1 - c4-pre-sort
2023-04-24T09:40:18Z
0xA5DF marked the issue as duplicate of #784
#2 - c4-pre-sort
2023-04-24T09:40:35Z
0xA5DF marked the issue as low quality report
#3 - c4-pre-sort
2023-04-24T09:42:19Z
0xA5DF marked the issue as duplicate of #983
#4 - c4-pre-sort
2023-04-24T09:54:41Z
0xA5DF marked the issue as not a duplicate
#5 - c4-pre-sort
2023-04-24T09:55:02Z
0xA5DF marked the issue as duplicate of #915
#6 - c4-judge
2023-05-17T18:48:55Z
hansfriese changed the severity to 2 (Med Risk)
#7 - c4-judge
2023-05-18T10:47:20Z
hansfriese marked the issue as satisfactory
🌟 Selected for report: juancito
Also found by: 0xAgro, 0xNorman, 0xSmartContract, 0xStalin, 0xTheC0der, 0xWaitress, 0xhacksmithh, 0xnev, 3dgeville, 8olidity, Arz, Aymen0909, BGSecurity, BRONZEDISC, Bauchibred, Bauer, BenRai, ChainHunters, ChrisTina, CodeFoxInc, DedOhWale, DishWasher, EloiManuel, IceBear, Inspex, Jorgect, Kaysoft, LeoGold, LewisBroadhurst, Madalad, MiloTruck, MohammedRizwan, Nyx, Polaris_tow, RaymondFam, SaharDevep, SanketKogekar, Sathish9098, SolidityATL, Udsen, W0RR1O, aria, ayden, berlin-101, bin2chen, catellatech, codeslide, crc32, decade, descharre, evmboi32, eyexploit, fatherOfBlocks, georgits, giovannidisiena, joestakey, karanctf, kodyvim, ltyu, lukris02, m9800, matrix_0wl, mov, mrpathfindr, nadin, niser93, p0wd3r, parlayan_yildizlar_takimi, pavankv, pontifex, qpzm, ravikiranweb3, rbserver, santipu_, shealtielanz, slvDev, tnevler, wonjun, xmxanuel, yixxas
22.6007 USDC - $22.60
3% Quorum seems to be too low. Being a qualified user has significant privileges. A qualified user can perpetually veto the addition of a new minter and can also restructure the system during dire situations. Considering that chain delegation is used, it is not difficult to reach a 3% quorum. In fact, there can be many delegates who will reach this threshold.
For example, if we only have 500 votes evenly distributed across 5 users A,B,C,D,E. Total votes is 9000.
A delegates to B who delegates to C who delegates to D who delegates to E.
In this case, C,D,E all have enough votes from their delegatees. C has 300, D has 400, E has 500. Because votes are stacked cumulatively and the delegate inherits all from delegatee without actually losing any votes of itself, quorum should be set to a higher amount.
8-10% seems a better choice of QUORUM.
#0 - c4-judge
2023-05-16T16:43:04Z
hansfriese marked the issue as grade-c
#1 - c4-judge
2023-05-18T06:18:08Z
hansfriese marked the issue as grade-b
#2 - yixxas
2023-05-18T20:40:40Z
With 5 downgraded issues to QA in #570, #628, #580, #634 and #630, along with this issue, I have 6 lows. Would greatly appreciate if this report can be reconsidered for grade-a :pray:
#3 - hansfriese
2023-05-19T04:50:25Z
6 lows are just below the threshhold, so it's B in the current setup. But this is very close anyway, so I will reconsider again.