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: 34/120
Findings: 1
Award: $207.11
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Krace
Also found by: 0xAadi, 0xpiken, AS, D1r3Wolf, PENGUN, SpicyMeatball, Yanchuan, bin2chen, d3e4, ether_sky, glcanvas, immeas, lanrebayode77, leegh, mojito_auditor, rvierdiiev, tnquanghuy0512
207.1122 USDC - $207.11
https://github.com/code-423n4/2023-11-canto/blob/335930cd53cf9a137504a57f1215be52c6d67cb3/1155tech-contracts/src/Market.sol#L150-L159 https://github.com/code-423n4/2023-11-canto/blob/335930cd53cf9a137504a57f1215be52c6d67cb3/1155tech-contracts/src/Market.sol#L280-L290
In the current implementation of the contract, the buyer's portion of the shareholder rewards from a purchase is not reflected in the buyer's rewards. A token holder who makes purchases may not receive the amount of holder rewards they are entitled to (a portion of their own fee is spent for the purchase that they rightfully earned). This vulnerability causes all the buyer's portions of shareholder rewards to be locked in the contract forever, and nobody can use or withdraw that fund. The amount of unusable funds will accumulate over time.
The buy()
function is designed in a way that the buyer is not eligible to receive their portion of the shareholder reward. Line numbers 154, 155 state that this is a deliberate design decision. However, shareholder rewards are calculated based on the token circulation, which includes a portion of the buyer Refer to line 290. All token holders, except the current buyer, can claim their portion of the shareholder reward. However, the buyer's portion of the reward will be locked in the contract forever due to the lack of functionality for the buyer or contract owner to claim it.
File: 1155tech-contracts/src/Market.sol 150: function buy(uint256 _id, uint256 _amount) external { 151: require(shareData[_id].creator != msg.sender, "Creator cannot buy"); 152: (uint256 price, uint256 fee) = getBuyPrice(_id, _amount); // Reverts for non-existing ID 153: SafeERC20.safeTransferFrom(token, msg.sender, address(this), price + fee); 154: @> // The reward calculation has to use the old rewards value (pre fee-split) to not include the fees of this buy 155: @> // The rewardsLastClaimedValue then needs to be updated with the new value such that the user cannot claim fees of this buy 156: uint256 rewardsSinceLastClaim = _getRewardsSinceLastClaim(_id); 157: // Split the fee among holder, creator and platform 158: @> _splitFees(_id, fee, shareData[_id].tokensInCirculation); 159: rewardsLastClaimedValue[_id][msg.sender] = shareData[_id].shareHolderRewardsPerTokenScaled; 280: function _splitFees( 281: uint256 _id, 282: uint256 _fee, 283: uint256 _tokenCount 284: ) internal { 285: uint256 shareHolderFee = (_fee * HOLDER_CUT_BPS) / 10_000; 286: uint256 shareCreatorFee = (_fee * CREATOR_CUT_BPS) / 10_000; 287: uint256 platformFee = _fee - shareHolderFee - shareCreatorFee; 288: shareData[_id].shareCreatorPool += shareCreatorFee; 289: if (_tokenCount > 0) { 290: @> shareData[_id].shareHolderRewardsPerTokenScaled += (shareHolderFee * 1e18) / _tokenCount;
rewardsSinceLastClaim
). This is based on the rewardsLastClaimedValue
for Alice. Line: 156shareHolderRewardsPerTokenScaled
in _splitFees()
. Line: 290rewardsLastClaimedValue
for Alice to the current shareHolderRewardsPerTokenScaled
. This will restrict claim her portion of the reward. Line: 159This indicates that the holder fee from Alice's purchase is excluded from her rewardsSinceLastClaim
when she makes a purchase. Consequently, this holder fee is irreversibly lost, and Alice is not receiving the rightful amount of rewards from her purchase. Moreover, nobody has the capability to withdraw this amount from the contract.
Manual Review
buy()
function to enable the buyer to claim their holder rewards.
OROther
#0 - c4-pre-sort
2023-11-20T08:50:21Z
minhquanym marked the issue as duplicate of #302
#1 - c4-judge
2023-11-28T22:45:03Z
MarioPoneder marked the issue as not a duplicate
#2 - c4-judge
2023-11-28T22:45:11Z
MarioPoneder marked the issue as duplicate of #9
#3 - c4-judge
2023-11-28T23:46:23Z
MarioPoneder marked the issue as satisfactory