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: 45/199
Findings: 3
Award: $149.54
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: cccz
Also found by: DishWasher, KIntern_NA, SolidityATL, ToonVH, giovannidisiena, joestakey, santipu_
93.1122 USDC - $93.11
https://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Equity.sol#L262-L264
In the Equity
contract, the calculateShares
function returns a wrong value when equity
is less than investment
.
calculateShares
is a view function that returns the number of shares that will be minted corresponding an investment
of ZCHF
tokens. This function calls directly calculateSharesInternal
that calculates the shares to be minted.
The function that actually makes the mint of shares happen is onTokenTransfer
. In this function there is a check to ensure that if equity <= amount
the shares minted will always be 1000e18
. This check is to assign 1000e18
shares minted to the first depositor.
The problem is that the calculateShares
function doesn't account for this so will return a different amount of shares when the equity
is less than investment
. This can happen before the first deposit or in a situation of low liquidity when the equity
is low.
calculateShares
function: https://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Equity.sol#L262-L264onTokenTransfer
: https://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Equity.sol#L247Manual review
Check in calculateShares
the same condition as in onTokenTransfer
.
Example:
function calculateShares(uint256 investment) public view returns (uint256) { uint256 equity = zchf.equity(); return equity <= investment ? 1000 * ONE_DEC18 : calculateSharesInternal(equity, investment); }
#0 - c4-pre-sort
2023-04-24T09:38:11Z
0xA5DF marked the issue as primary issue
#1 - 0xA5DF
2023-04-24T09:39:16Z
This can happen before the first deposit or in a situation of low liquidity when the equity is low.
For first depositor this isn't an issue since next deposits are relative to the first one. Situations where there's low liquidity might be a valid issue, will need sponsor's input on this
#2 - c4-pre-sort
2023-04-24T09:43:22Z
0xA5DF marked the issue as duplicate of #983
#3 - c4-judge
2023-05-18T04:59:56Z
hansfriese marked the issue as duplicate of #396
#4 - c4-judge
2023-05-18T05:21:49Z
hansfriese marked the issue as duplicate of #396
#5 - c4-judge
2023-05-18T13:35:27Z
hansfriese marked the issue as satisfactory
33.835 USDC - $33.83
https://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Frankencoin.sol#L146-L151
In the Frankencoin system a minter that hasn't been denied during the application period can't be revoked in case of a private keys leak or a malicious minter.
Currently, when a minter is suggested and not denied during the application period, it will be able to mint tokens and transfer tokens of users as it please so it has to be a trusted party. The problem is that in case the private keys of a minter are leaked or this minter turns out to be malicious, it has the power to mint mint infinite tokens or burn them. The FPS holders should have a mechanism to revoke the privileges of a minter if any of these happens.
https://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Frankencoin.sol#L146-L151
Manual Review
Implement a mechanism to revoke the privileges of a minter in case something unexpected happens in order to prevent the destruction of the Frankencoin ecosystem.
#0 - c4-pre-sort
2023-04-21T14:20:44Z
0xA5DF marked the issue as duplicate of #230
#1 - c4-judge
2023-05-18T13:40:26Z
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
Ownable
contracthttps://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Ownable.sol#L16-L17
In the Ownable
contract, that is a modified copy from the Openzeppelin Ownable
contract, the comments state that the owner will be by default the contract deployer. This statement is false because in the contract there's no constructor so the contract that inherit form this one must set the owner manually.
This issue has no real impact because the Position
contract sets the owner in the constructor but this comment can lead to misunderstanding with other devs or with future versions of Frankencoin leading to a contract without owner.
checkQualified
won't revert when there are no shares minted or if it's called in the same block as the first shares are mintedhttps://github.com/Frankencoin-ZCHF/FrankenCoin/blob/main/contracts/Equity.sol#L209-L212
In the Equity
contract, the checkQualified
function should revert when the sender
doesn't hold at least the 3% of the votes. This doesn't happen when there're not shares minted of when the function is called in the same block as the transaction that minted the first shares.
This is because the function calls totalVotes
, and this function makes the following calculation:
totalVotesAtAnchor + totalSupply() * (anchorTime() - totalVotesAnchorTime)
So, when there are no shares minted, totalVotesAtAnchor
and totalSupply
will be zero making the function to not revert as it should.
Also, in the same block as the first deposit, totalVotesAtAnchor
will still be zero and anchorTime() - totalVotesAnchorTime
will also result in zero making the function to not revert as it should.
#0 - 0xA5DF
2023-04-27T09:55:19Z
#2 is dupe of #952
#1 - c4-judge
2023-05-16T16:38:16Z
hansfriese marked the issue as grade-b