Platform: Code4rena
Start Date: 05/07/2023
Pot Size: $390,000 USDC
Total HM: 136
Participants: 132
Period: about 1 month
Judge: LSDan
Total Solo HM: 56
Id: 261
League: ETH
Rank: 29/132
Findings: 9
Award: $1,952.43
🌟 Selected for report: 0
🚀 Solo Findings: 1
🌟 Selected for report: zzzitron
Also found by: 0xRobocop, 0xSky, 0xrugpull_detector, KIntern_NA, RedOneN, bin2chen, carrotsmuggler, chaduke, kutugu, minhtrng, mojito_auditor, plainshift, zzebra83
23.4714 USDC - $23.47
Detailed description of the impact of this finding.
BigBang.getDebtRate()
does not work for the case of _currentDebt < debtStartPoint
. This is because when
_currentDebt < debtStartPoint
, the function will have an underflow and revert.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
BigBang.getDebtRate()
will get the current debt rate.
However, the function does not deal with the case of _currentDebt < debtStartPoint
, since the following line will revert under this case:
uint256 debtPercentage = ((_currentDebt - debtStartPoint) * DEBT_PRECISION) / (_maxDebtPoint - debtStartPoint);
As a result, the function is not fully functioning for all cases.
VSCode
Deal with the case of _currentDebt < debtStartPoint
as follows:
function getDebtRate() public view returns (uint256) { if (_isEthMarket) return penrose.bigBangEthDebtRate(); // default 0.5% if (totalBorrow.elastic == 0) return minDebtRate; uint256 _ethMarketTotalDebt = BigBang(penrose.bigBangEthMarket()) .getTotalDebt(); uint256 _currentDebt = totalBorrow.elastic; uint256 _maxDebtPoint = (_ethMarketTotalDebt * debtRateAgainstEthMarket) / 1e18; + if (_currentDebut < debutSgtartPoint) return minDebtRate; if (_currentDebt >= _maxDebtPoint) return maxDebtRate; uint256 debtPercentage = ((_currentDebt - debtStartPoint) * DEBT_PRECISION) / (_maxDebtPoint - debtStartPoint); uint256 debt = ((maxDebtRate - minDebtRate) * debtPercentage) / DEBT_PRECISION + minDebtRate; if (debt > maxDebtRate) return maxDebtRate; return debt; }
Math
#0 - c4-pre-sort
2023-08-04T22:22:41Z
minhquanym marked the issue as duplicate of #1370
#1 - c4-pre-sort
2023-08-04T22:30:29Z
minhquanym marked the issue as duplicate of #1046
#2 - c4-judge
2023-09-18T13:19:07Z
dmvt marked the issue as partial-50
🌟 Selected for report: carrotsmuggler
471.2071 USDC - $471.21
Detailed description of the impact of this finding.
BalancerStrategy._withdraw()
calculates toWithdraw
as the number of shares of the pool instead of the amount of wrappedNative
tokens, as a result, the function will most likely fail all the time.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
BalancerStrategy._withdraw()
allows one to withdraw wrappedNative
tokens from the BalanceStrategy.
There are two cases: 1) there is sufficient balance in the strategy to cover the withdrawl amount of amount
; 2) the balance of wrappedNative
tokens in the BalanceStrategy is insufficient to cover amount
, in this case, the function will withdraw from the underlying vault the deficit.
However, when calculating the amount that needs to be withdrawn from the underlying vault, toWithdraw
is calculated as number of shares of the pool instead of the amount of wrappedNative
tokens. Meanwhile, function _vaultWithdraw(toWithdraw)
expect toWithdraw
to be the output amount of wrappedNative tokens, not the number of shares of the input.
uint256 toWithdraw = (((amount - queued) * (10 ** decimals)) / pricePerShare);
as a result, the value of toWithdraw
could be less than it is supposed to be. As a result, even after the withdrawal from the underlying vault, there is still insufficient balance to cover the needed amount
, and the function BalancerStrategy._withdraw()
will likely fail all the time.
VSCode
Calculate toWithdraw
as the amount of wrappedNative
tokens:
function _withdraw( address to, uint256 amount ) internal override nonReentrant { uint256 available = _currentBalance(); require(available >= amount, "BalancerStrategy: amount not valid"); uint256 queued = wrappedNative.balanceOf(address(this)); if (amount > queued) { uint256 pricePerShare = pool.getRate(); uint256 decimals = IStrictERC20(address(pool)).decimals(); - uint256 toWithdraw = (((amount - queued) * (10 ** decimals)) / - pricePerShare); + uint256 toWithdraw = (amount - queued) * (10 ** decimals); _vaultWithdraw(toWithdraw); } require( amount <= wrappedNative.balanceOf(address(this)), "BalancerStrategy: not enough" ); wrappedNative.safeTransfer(to, amount); updateCache(); emit AmountWithdrawn(to, amount); }
Math
#0 - c4-pre-sort
2023-08-05T02:29:52Z
minhquanym marked the issue as primary issue
#1 - c4-sponsor
2023-08-20T15:41:35Z
0xRektora marked the issue as sponsor confirmed
#2 - c4-judge
2023-09-19T11:34:23Z
dmvt changed the severity to 3 (High Risk)
#3 - c4-judge
2023-09-19T11:34:34Z
dmvt marked issue #978 as primary and marked this issue as a duplicate of 978
#4 - c4-judge
2023-09-19T11:34:41Z
dmvt marked the issue as satisfactory
🌟 Selected for report: mojito_auditor
Also found by: KIntern_NA, Udsen, bin2chen, chaduke, jasonxiale, kutugu, peakbolt, rvierdiiev
37.0991 USDC - $37.10
Detailed description of the impact of this finding.
TapOFT.extractTAP() might mint more TAP amount than allowed. This is because the allowed limit should be emissionForWeek[week] - mintedInWeek[week]
instead of emissionForWeek[week]
. Otherwise, a malicious or careless minter might call TapOFT.extractTAP()
multiple times and mint more TAP than allowed.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
TapOFT.extractTAP() allows the minter to mint TAP but this amount should not exceed the limit.
However, the limit checked at L225 is emissionForWeek[week]
. The correct limit should be emissionForWeek[week] - mintedInWeek[week]
.
As a result, a malicious or careless minter might call TapOFT.extractTAP()
multiple times and mint more TAP than allowed.
My following POC code confirms my finding: the minter calls TapOFT.extractTAP()
four times and as a result, minted 1876631856000000000000000 of Tap, which is 4 times of the allowed amount: 469157964000000000000000.
Please pay attention to function testTapOFT()
.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "../contracts/tokens/TapOFT.sol"; import "../contracts/governance/twTAP.sol"; contract TapiocaTest is Test { function setUp() public { } function testTapOFT() public{ address _lzEndPoint = address(111); address _contributors = address(112); address _earlySupporters = address(113); address _lbp = address(114); address _dao = address(115); address _airdrop = address(116); uint256 _governanceChainId = block.chainid; address _conservator = address(117); address _owner = msg.sender; TapOFT _tapoft = new TapOFT(_lzEndPoint, _contributors, _earlySupporters, _earlySupporters, _lbp, _dao, _airdrop, _governanceChainId, _conservator ); uint256 e1 = _tapoft.emitForWeek(); console2.log("emitforWeek week 1: %d", e1); vm.prank(_conservator); _tapoft.setMinter(address(555)); vm.startPrank(address(555)); _tapoft.extractTAP(address(666), 469157964000000000000000); _tapoft.extractTAP(address(666), 469157964000000000000000); _tapoft.extractTAP(address(666), 469157964000000000000000); _tapoft.extractTAP(address(666), 469157964000000000000000); vm.stopPrank(); console2.log("total minted for 666: %d", _tapoft.balanceOf(address(666))); } }
VSCode
Use the limit emissionForWeek[week] - mintedInWeek[week]
instead:
function extractTAP(address _to, uint256 _amount) external notPaused { require(msg.sender == minter, "unauthorized"); require(_amount > 0, "amount not valid"); uint256 week = _timestampToWeek(block.timestamp); - require(emissionForWeek[week] >= _amount, "exceeds allowable amount"); + require(emissionForWeek[week] >= mintedInWeek[week] + _amount, "exceeds allowable amount"); _mint(_to, _amount); mintedInWeek[week] += _amount; emit Minted(msg.sender, _to, _amount); }
Math
#0 - c4-pre-sort
2023-08-04T23:11:38Z
minhquanym marked the issue as duplicate of #728
#1 - c4-judge
2023-09-27T12:00:18Z
dmvt marked the issue as satisfactory
🌟 Selected for report: 0xadrii
Also found by: 0xWaitress, KIntern_NA, Kaysoft, Madalad, chaduke, dontonka, rvierdiiev, vagrant, wangxx2026
30.0503 USDC - $30.05
Detailed description of the impact of this finding. YieldBox.depositETHAsset() fails to compare amount = msg.value, leading to 1) user A loss of funds; or 2) user B steal funds from user A.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
YieldBox.depositETHAsset() allows a user to deposit ETH into the YieldBox (and thus the strategy for the wrappedNative tokens):
However, the function fails to compare the specified input amount is equal to msg.value, leading to cases:
User A specifies the amount is smaller than msg.value. For example, amount = 1e18, and msg.value = 2e18. In this case, user A will lose 1e18 to the contract.
User B specifies the amount is larger than msg.value. For example, amount = 4e18, and msg.value = 3e18. Although there is a deficit, it can be filled by the the left ETH by user A. In other words, user B steals the ETH that is left by user A and deposit more than he is supposed to.
Both cases are not desirable. One need to compare msg.value == amount.
VSCode
Make sure msg.value == amount so that the depositor will never gain nor loss any funds.
ETH-Transfer
#0 - c4-pre-sort
2023-08-05T05:43:27Z
minhquanym marked the issue as duplicate of #983
#1 - c4-judge
2023-09-12T19:43:47Z
dmvt marked the issue as unsatisfactory: Out of scope
#2 - c4-judge
2023-09-12T19:50:57Z
dmvt marked the issue as satisfactory