sherlock-audit / 2024-04-titles-judging

1 stars 1 forks source link

Varun_05 - Whenever a new work is added to a existing edition it overrites the referrers[editon] value which denies the previous referrer from fees which he should recieve. #322

Closed sherlock-admin3 closed 2 months ago

sherlock-admin3 commented 2 months ago

Varun_05

high

Whenever a new work is added to a existing edition it overrites the referrers[editon] value which denies the previous referrer from fees which he should recieve.

Summary

Whenever a new work is added to a existing edition it overrites the referrers[editon] value which denies the previous referrer from fees which he should receive.

Vulnerability Detail

Main function which is used to publish new edition or works is the internal _publish function

function _publish(Edition edition_, WorkPayload memory work_, address referrer_)
        internal
        returns (uint256 tokenId)
    {
        // Publish the new Work in the Edition
        // wake-disable-next-line reentrancy
        tokenId = edition_.publish(
            work_.creator.target,
            work_.maxSupply,
            work_.opensAt,
            work_.closesAt,
            work_.attributions,
            work_.strategy,
            work_.metadata
        );

        // Collect the creation fee
        // wake-disable-next-line reentrancy
        feeManager.collectCreationFee{value: msg.value}(edition_, tokenId, msg.sender);

        // Create the fee route for the new Work
        // wake-disable-next-line reentrancy
        Target memory feeReceiver = feeManager.createRoute(
            edition_, tokenId, _attributionTargets(work_.attributions), referrer_
        );

        // Set the royalty target for the new Work
        // wake-disable-next-line reentrancy
        edition_.setRoyaltyTarget(tokenId, feeReceiver.target);
    }

The following line of code sets the referrers for a particular edition whenever a new work is published

Target memory feeReceiver = feeManager.createRoute(
            edition_, tokenId, _attributionTargets(work_.attributions), referrer_
        );

CreateRoute function is as follows

function createRoute(
        IEdition edition_,
        uint256 tokenId_,
        Target[] calldata attributions_,
        address referrer_
    ) external onlyOwnerOrRoles(ADMIN_ROLE) returns (Target memory receiver) {
        Target memory creator = edition_.node(tokenId_).creator;

        if (attributions_.length == 0) {
            // No attributions, pay the creator directly
            receiver = creator;
        } else {
            // Distribute the fee among the creator and attributions
            (address[] memory targets, uint256[] memory revshares) = _buildSharesAndTargets(
                creator, attributions_, edition_.feeStrategy(tokenId_).revshareBps
            );

            // Create the split. The protocol retains "ownership" to enable future use cases.
            receiver = Target({
                target: splitFactory.createSplit(
                    SplitV2Lib.Split({
                        recipients: targets,
                        allocations: revshares,
                        totalAllocation: 1e6,
                        distributionIncentive: 0
                    }),
                    address(this),
                    creator.target
                    ),
                chainId: creator.chainId
            });
        }

        _feeReceivers[getRouteId(edition_, tokenId_)] = receiver;
        referrers[edition_] = referrer_;
    }

As can be seen from the following line referrers[edition] = referrer it always overrites the exisiting referrer to new referrer. The issue here is whenever a new token is minted from the edition contract the user pays fees and some of that fees goes to the referrers[edition_] which is equal to the collectionReferrer share which can be seen from the following collectFee and splitprotocol fees in the fee manager contract

function _collectMintFee(
        IEdition edition_,
        uint256 tokenId_,
        uint256 amount_,
        address payer_,
        address referrer_,
        Fee memory fee_
    ) internal {
        if (fee_.amount == 0) return;

        // For free mints:
        // - Protocol Share = 1/3 of flat fee
        // - Edition Share = 2/3 of flat fee
        //
        // For priced mints:
        // - Protocol Share = 100% of flat fee, shared as follows:
        // - Edition Share = 100% of creator-specified mint cost, 0% of flat fee
        //
        // In both cases, the protocol and edition shares may be split as follows:
        // - Protocol Share
        //   - If a referred mint, mint referrer gets 50% of the protocol share
        //   - If a referred collection, collection referrer gets 25% of the protcol share
        //   - Protocol fee receiver gets the remainder of the protocol share
        // - Edition Share
        //   - Attributions equally split 25% of the edition share, if applicable
        //   - Creator gets the remainder of the edition share

        uint256 protocolFee = protocolFlatFee * amount_;
        uint256 protocolShare;
        if (fee_.amount == protocolFee) {
            protocolShare = protocolFee * protocolFeeshareBps / MAX_BPS;
        } else {
            protocolShare = protocolFee;
        }

        _route(
            Fee({asset: fee_.asset, amount: fee_.amount - protocolShare}),
            _feeReceivers[getRouteId(edition_, tokenId_)],
            payer_
        );

        uint256 referrerShare =
            _splitProtocolFee(edition_, fee_.asset, protocolShare, payer_, referrer_);
        emit FeeCollected(address(edition_), tokenId_, fee_.asset, fee_.amount, referrerShare);
    }

    function _splitProtocolFee(
        IEdition edition_,
        address asset_,
        uint256 amount_,
        address payer_,
        address referrer_
    ) internal returns (uint256 referrerShare) {
        // The creation and mint referrers earn 25% and 50% of the protocol's share respectively, if applicable
        uint256 mintReferrerShare = getMintReferrerShare(amount_, referrer_);
   ==>     uint256 collectionReferrerShare = getCollectionReferrerShare(amount_, referrers[edition_]);
        referrerShare = mintReferrerShare + collectionReferrerShare;

        _route(
            Fee({asset: asset_, amount: amount_ - referrerShare}),
            Target({target: protocolFeeReceiver, chainId: block.chainid}),
            payer_
        );

        _route(
            Fee({asset: asset_, amount: mintReferrerShare}),
            Target({target: referrer_, chainId: block.chainid}),
            payer_
        );

        _route(
   ==>         Fee({asset: asset_, amount: collectionReferrerShare}),
            Target({target: referrer_, chainId: block.chainid}),
            payer_
        );
    }

So if a new work is published then the previous referrer won't receive any fee as he would be overriden.It is a issue becasue hadn't new work been published then the previous referrer would have received his share of mint fee but now the referrer gets overridden everytime a new work is published.

Impact

An old referrer can be denied of his share of fee which he is applicable to receive. Here loss of funds is for the previous referres.

Code Snippet

https://github.com/sherlock-audit/2024-04-titles/blob/d7f60952df22da00b772db5d3a8272a988546089/wallflower-contract-v2/src/fees/FeeManager.sol#L159

Tool used

Manual Review

Recommendation

Don't what can be exact mitigation for this one , one can be to stores referrers[edition] as a mapping from a edition to an array of referrers which are added whenever a new work is published and distribute the collection referrer share fees to all of them equally.

Duplicate of #265