Platform: Code4rena
Start Date: 13/11/2023
Pot Size: $24,500 USDC
Total HM: 3
Participants: 120
Period: 4 days
Judge: 0xTheC0der
Id: 306
League: ETH
Rank: 80/120
Findings: 1
Award: $4.08
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: chaduke
Also found by: 0xpiken, Bauchibred, Matin, MohammedRizwan, MrPotatoMagic, OMEN, Pheonix, SandNallani, T1MOH, Topmark, ZanyBonzy, adriro, aslanbek, ayden, bareli, bart1e, bin2chen, btk, cheatc0d3, codynhat, critical-or-high, d3e4, erebus, firmanregar, hunter_w3b, jasonxiale, kaveyjoe, ksk2345, lsaudit, max10afternoon, merlinboii, nailkhalimov, osmanozdemir1, peanuts, pep7siup, pontifex, sbaudh6, shenwilly, sl1, tourist, wisdomn_, young, zhaojie
4.0797 USDC - $4.08
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/bonding_curve/LinearBondingCurve.sol#L42 https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/bonding_curve/LinearBondingCurve.sol#L30
Precision loss in Log2 calculation leads to wrong fee calculation in the contract LinearBondingCurve
The current Log2 calculation which is inspired from the Solady library, uses the most significant bit scheme:
function log2(uint256 x) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // forgefmt: disable-next-item r := or( r, byte( and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), 0x0706060506020504060203020504030106050205030304010505030400000000 ) ) } }
This algorithm is vulnerable to round-off error which rounds the number to its floor. For example, if the actual Log2 of a number equals to 20.999
this algorithm will return 20
. By default, this algorithm works well with high decimal numbers such as wads (~18) or rays (~27) as the discrepancy makes not such a big difference. If the number is relatively small, the slope and thus the discrepancy is bigger.
If we look at the application of this function in the bonding curve based algorithm, we will find out that it is being used in the function getFee()
in the contract LinearBondingCurve:
function getFee(uint256 shareCount) public pure override returns (uint256) { uint256 divisor; if (shareCount > 1) { divisor = log2(shareCount); } else { divisor = 1; } // 0.1 / log2(shareCount) return 1e17 / divisor; }
As we can see, the log2()
is used for the divisor
calculation with the input of the shareCount
. This number shows the number of tokens a person has. The vulnerable part starts from here, as the abovementioned discrepancy would decrease the precision.
For a POC, let's consider these scenarios:
Case Number | Share Count | Current Log2() | Actual Log2() | Current getFee() | Actual getFee() | Discrepancy (%) |
---|---|---|---|---|---|---|
[01] | 3 | 1 | 1.585 | 1e17 | 6.3e16 | 58 |
[02] | 12 | 3 | 3.585 | 3.3e16 | 2.5e16 | 32 |
[03] | 15 | 3 | 3.907 | 3.3e16 | 2.7e16 | 22 |
These errors shows a large precision loss in the fees which currently charges the users more than the actual ones which is due to the fact that this mechanism doesn't ceil a number which is near to its nearest integer number. I understand that this is due to the fact that Solidity doesn't support decimal numbers, though, for a fair fee calculation, the system should check the nearest number and decide to round the number to its floor or ceil.
Manual Review
Short-term: Consider checking the nearest integer number and fairly round the number (especially if the number is less than 8
)
Long-term: Use a more accurate Taylor-based (with third order truncation error to lower the gas) considering a basis point of 1000
.
Math
#0 - c4-pre-sort
2023-11-18T13:03:27Z
minhquanym marked the issue as duplicate of #25
#1 - c4-judge
2023-11-29T15:25:47Z
MarioPoneder changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-11-29T15:26:43Z
MarioPoneder marked the issue as satisfactory
#3 - c4-judge
2023-11-30T20:57:28Z
MarioPoneder changed the severity to QA (Quality Assurance)
#4 - c4-judge
2023-11-30T20:58:54Z
MarioPoneder marked the issue as grade-b