Protocol may lose fee due to wrong calculation of referral fee
Medium
Finding description and impact
When purchasing listed tokens with a referral, users must pay fees. In case there a referrer, the referral fees is are calculated and deduct from the fees the buyer pays to the protocol in the SecondSwap_Marketplace::_handleTransfers
function.
File: contracts/SecondSwap_Marketplace.sol#L451-L493 function _handleTransfers( Listing storage listing, uint256 _amount, uint256 discountedPrice, uint256 bfee, uint256 sfee, address _referral ) private returns (uint256 buyerFeeTotal, uint256 sellerFeeTotal, uint256 referralFeeCost) { uint256 baseAmount = (_amount * discountedPrice) / uint256( 10 ** ( IERC20Extended( address( IVestingManager(IMarketplaceSetting(marketplaceSetting).vestingManager()) .getVestingTokenAddress(listing.vestingPlan) ) ).decimals() ) ); // 3.1. Rounding issue leads to total drain of vesting entries require(baseAmount > 0, "SS_Marketplace: Amount too little"); // 3.1. Rounding issue leads to total drain of vesting entries buyerFeeTotal = (baseAmount * bfee) / BASE; sellerFeeTotal = (baseAmount * sfee) / BASE; IERC20(listing.currency).safeTransferFrom(msg.sender, address(this), (baseAmount + buyerFeeTotal)); // 3.6. DOS caused by the use of transfer and transferFrom functions referralFeeCost = 0; if (_referral != address(0) && listing.whitelist == address(0)) { referralFeeCost = buyerFeeTotal - (baseAmount * bfee * IMarketplaceSetting(marketplaceSetting).referralFee()) / (BASE * BASE); } IERC20(listing.currency).safeTransfer(listing.seller, (baseAmount - sellerFeeTotal)); // 3.6. DOS caused by the use of transfer and transferFrom functions uint256 feeCollectorTotal = (buyerFeeTotal + sellerFeeTotal); IERC20(listing.currency).safeTransfer( IMarketplaceSetting(marketplaceSetting).feeCollector(), feeCollectorTotal ); // 3.6. DOS caused by the use of transfer and transferFrom functions }
referralFeeCost
is the part of fee that goes to the referral.
File: contracts/SecondSwap_MarketplaceSetting.sol#L36-L39 /** * @notice Percentage of fees allocated to referrals (in basis points) */ uint256 public referralFee;
The issue here is that the referralFeeCost
are wrongly calculated based on what referralFee
represents. Which is the percentage of fees allocated to referrals. Below is how referralFeeCost
should be calculated.
referralFeeCost = (buyerFeeTotal * IMarketplaceSetting(marketplaceSetting).referralFee()) / BASE = (((baseAmount * bfee) / BASE) * IMarketplaceSetting(marketplaceSetting).referralFee()) / BASE = (baseAmount * bfee * IMarketplaceSetting(marketplaceSetting).referralFee()) / (BASE * BASE)
Instead, the current calculation substract the right value from buyerFeeTotal
.
The impact here is twofold :
- The more
referralFee
is closed to 0%, the more referrers will get and the protocol will lose money. - The more
referralFee
is closed to 100%, the less referrers will get than the should.
Proof of Concept
- https://github.com/code-423n4/2024-12-secondswap/blob/214849c3517eb26b31fe194bceae65cb0f52d2c0/contracts/SecondSwap_Marketplace.sol#L479-L484
- https://github.com/code-423n4/2024-12-secondswap/blob/214849c3517eb26b31fe194bceae65cb0f52d2c0/contracts/SecondSwap_MarketplaceSetting.sol#L36-L39
Recommended mitigation steps
File: contracts/SecondSwap_Marketplace.sol#L451-L493 function _handleTransfers( Listing storage listing, uint256 _amount, uint256 discountedPrice, uint256 bfee, uint256 sfee, address _referral ) private returns (uint256 buyerFeeTotal, uint256 sellerFeeTotal, uint256 referralFeeCost) { uint256 baseAmount = (_amount * discountedPrice) / uint256( 10 ** ( IERC20Extended( address( IVestingManager(IMarketplaceSetting(marketplaceSetting).vestingManager()) .getVestingTokenAddress(listing.vestingPlan) ) ).decimals() ) ); // 3.1. Rounding issue leads to total drain of vesting entries require(baseAmount > 0, "SS_Marketplace: Amount too little"); // 3.1. Rounding issue leads to total drain of vesting entries buyerFeeTotal = (baseAmount * bfee) / BASE; sellerFeeTotal = (baseAmount * sfee) / BASE; IERC20(listing.currency).safeTransferFrom(msg.sender, address(this), (baseAmount + buyerFeeTotal)); // 3.6. DOS caused by the use of transfer and transferFrom functions referralFeeCost = 0; if (_referral != address(0) && listing.whitelist == address(0)) { referralFeeCost = -- buyerFeeTotal - (baseAmount * bfee * IMarketplaceSetting(marketplaceSetting).referralFee()) / (BASE * BASE); } IERC20(listing.currency).safeTransfer(listing.seller, (baseAmount - sellerFeeTotal)); // 3.6. DOS caused by the use of transfer and transferFrom functions uint256 feeCollectorTotal = (buyerFeeTotal + sellerFeeTotal); IERC20(listing.currency).safeTransfer( IMarketplaceSetting(marketplaceSetting).feeCollector(), feeCollectorTotal ); // 3.6. DOS caused by the use of transfer and transferFrom functions }