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: 98/199
Findings: 2
Award: $22.67
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: decade
Also found by: 0x3b, 0xDACA, 0xWaitress, 0xWeiss, 0xkaju, Arz, Aymen0909, BPZ, EloiManuel, HaCk0, J4de, Jerry0x, Jiamin, John, Juntao, Kek, Lalanda, MiloTruck, Mukund, PNS, RedTiger, Ruhum, Satyam_Sharma, ToonVH, Tricko, Udsen, ak1, anodaram, bin2chen, carrotsmuggler, cccz, circlelooper, deadrxsezzz, giovannidisiena, jasonxiale, joestakey, juancito, karanctf, kenta, kodyvim, ladboy233, lil_eth, lukino, markus_ether, marwen, mrpathfindr, nobody2018, parlayan_yildizlar_takimi, peakbolt, ravikiranweb3, rbserver, rvierdiiev, silviaxyz, volodya, zhuXKET, zzebra83
0.0748 USDC - $0.07
The restructureCapTable() function in Equity.sol
is designed to allow FPS shareholders to be able to restructure capital by burning the tokens of an array of addressesToWipe.
Because of an error in the loop of this function, only the 0th index in the array, address[] calldata addressesToWipe
will be targeted for restructuring rather than addressesToWipe.length
as intended by the protocol.
Let us examine the restructureCapTable() function.
function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public { require(zchf.equity() < MINIMUM_EQUITY); checkQualified(msg.sender, helpers); for (uint256 i = 0; i<addressesToWipe.length; i++){ address current = addressesToWipe[0]; _burn(current, balanceOf(current)); } }
As you can see, the current address targeted will always be addressesToWipe[0]; this means, all other addresses in the array address[] calldata addressesToWipe will never be targeted, therefore their tokens will never be fully restructured.
Manual Review
Correct the function to iterate through all addresses in addressesToWipe
like so:
function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public { require(zchf.equity() < MINIMUM_EQUITY); checkQualified(msg.sender, helpers); for (uint256 i = 0; i<addressesToWipe.length; i++){ address current = addressesToWipe[i]; _burn(current, balanceOf(current)); } }
#0 - c4-pre-sort
2023-04-20T14:25:04Z
0xA5DF marked the issue as duplicate of #941
#1 - c4-judge
2023-05-18T14:29:40Z
hansfriese marked the issue as satisfactory
#2 - c4-judge
2023-05-18T14:30:49Z
hansfriese changed the severity to 2 (Med Risk)
🌟 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
Instances include:
The recursive function calls itself, if the array of helpers has a duplicate, the function will continue to call itself until the transaction runs out of gas. Worst case, if no gas limit is specified, the user will run out of gas when calling this function.
function canVoteFor(address delegate, address owner) internal view returns (bool) { if (owner == delegate){ //@audit might want to check who the owner is in this case. Most likely the owner of the contract??? return true; } else if (owner == address(0x0)){ return false; } else { return canVoteFor(delegate, delegates[owner]); //@audit this function calls itself ? } }
Recommendation:
Include a condition to ensure the previous address being targeted is not equal to the current address.
unction canVoteFor(address delegate, address owner) internal view returns (bool) { if (owner == delegate) { return true; } else if (owner == address(0x0)) { return false; } else { // Keep track of visited addresses to prevent infinite loops mapping (address => bool) visited; visited[owner] = true; address currentOwner = delegates[owner]; while (currentOwner != delegate && currentOwner != address(0x0) && !visited[currentOwner]) { visited[currentOwner] = true; currentOwner = delegates[currentOwner]; } return currentOwner == delegate; } }
#0 - 0xA5DF
2023-04-26T17:01:02Z
MIght be a dupe of #640
#1 - c4-judge
2023-05-18T05:58:53Z
hansfriese marked the issue as grade-b