sherlock-audit / 2024-06-boost-aa-wallet-judging

3 stars 1 forks source link

SyncCode2017 - Boost creator can collect all the fees by setting referralFee to 9_000 and give claimants his address as referrer_ address #158

Open sherlock-admin3 opened 2 months ago

sherlock-admin3 commented 2 months ago

SyncCode2017

High

Boost creator can collect all the fees by setting referralFee to 9000 and give claimants his address as referrer address

Summary

The boost creator can set the value of referralFee to 9_000 when creating the boost. The BoostCore::referralFee (the base fee) is set to 1000 in line 70,

https://github.com/sherlock-audit/2024-06-boost-aa-wallet/blob/main/boost-protocol/packages/evm/contracts/BoostCore.sol#L70

and added to the boost creator input in line 122,

https://github.com/sherlock-audit/2024-06-boost-aa-wallet/blob/main/boost-protocol/packages/evm/contracts/BoostCore.sol#L122

This will make the BoostCore::referralFee to be 10_000 (equal to the BoostCore::FEE_DENOMINATOR) ensuring that 100% of the fees collected when claimants claim their incentives are sent to the referrer address. To get the fees, the boost creator just need to ensure claimants use his address as referrer_ address. The protocol will never receive any fee for this particular boost.

Root Cause

Maximum value for BoostCore::referralFee was not set, allowing boost creators to allocate unlimited fraction of the fees to the referrer.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

The protocol will receive no fees as all the fees will continuously be sent to the referrer_ address.

PoC

Please copy the code below into BoostCore.t.sol and run the test.


    uint64 public constant boostAdditionalReferralFee = 9_000; // additional 90%
    uint256 public constant PRECISION = 10_000;
    uint256 public constant BASE_FEE = 1_000; // 10%
    bytes invalidCreateCalldata =
        LibZip.cdCompress(
            abi.encode(
                BoostCore.InitPayload({
                    budget: budget,
                    action: action,
                    validator: BoostLib.Target({
                        isBase: true,
                        instance: address(0),
                        parameters: ""
                    }),
                    allowList: allowList,
                    incentives: _makeIncentives(1),
                    protocolFee: 500, // 5%
                    referralFee: boostAdditionalReferralFee, // 90%
                    maxParticipants: 10_000,
                    owner: address(1)
                })
            )
        );

    function testClaimIncentive_ReferralTakesAllFees_audit() public {
        uint256 claimFee = 0.000075 ether;
        // Create a Boost first
        boostCore.createBoost(invalidCreateCalldata);

        // Mint an ERC721 token to the claimant (this contract)
        uint256 tokenId = 1;
        mockERC721.mint{value: 0.1 ether}(address(this));
        mockERC721.mint{value: 0.1 ether}(address(this));
        mockERC721.mint{value: 0.1 ether}(address(this));

        // Prepare the data payload for validation
        bytes memory data = abi.encode(address(this), abi.encode(tokenId));
        address referralAddress = makeAddr("referral");
        address protocolFeeReceiver = boostCore.protocolFeeReceiver();
        uint256 initialProtocolFeeReceiverBalance = protocolFeeReceiver.balance;
        // Claim the incentive
        boostCore.claimIncentive{value: claimFee}(0, 0, referralAddress, data);

        uint256 actualReferrerBalance = referralAddress.balance;
        uint256 finalProtocolFeeReceiverBalance = protocolFeeReceiver.balance;
        // check referral balance
        assertEq(actualReferrerBalance, claimFee);
        // check protocol fee receiver balance
        assertEq(
            (finalProtocolFeeReceiverBalance -
                initialProtocolFeeReceiverBalance),
            0
        );
        // Check the claims
        BoostLib.Boost memory boost = boostCore.getBoost(0);
        ERC20Incentive _incentive = ERC20Incentive(
            address(boost.incentives[0])
        );
        assertEq(_incentive.claims(), 1);
    }

Mitigation

Set a maximum value for BoostCore::referralFee and refactor BoostCore::createBoost as shown below.

+ uint64 public constant MAX_REFERRER_FEE = 5000; // should be any value below 10_000
 function createBoost(bytes calldata data_)
        external
        canCreateBoost(msg.sender)
        nonReentrant
        returns (BoostLib.Boost memory)
    {
        InitPayload memory payload_ = abi.decode(data_.cdDecompress(), (InitPayload));

        // Validate the Budget
        _checkBudget(payload_.budget);

        // Initialize the Boost
        BoostLib.Boost storage boost = _boosts.push();
        boost.owner = payload_.owner;
        boost.budget = payload_.budget;
        boost.protocolFee = protocolFee + payload_.protocolFee;
        boost.referralFee = referralFee + payload_.referralFee;
+       require(boost.referralFee <= MAX_REFERRER_FEE, "referralFee is too high");
        boost.maxParticipants = payload_.maxParticipants;

        // Setup the Boost components
        boost.action = AAction(_makeTarget(type(AAction).interfaceId, payload_.action, true));
        boost.allowList = AAllowList(_makeTarget(type(AAllowList).interfaceId, payload_.allowList, true));
        boost.incentives = _makeIncentives(payload_.incentives, payload_.budget);
        boost.validator = AValidator(
            payload_.validator.instance == address(0)
                ? boost.action.supportsInterface(type(AValidator).interfaceId) ? address(boost.action) : address(0)
                : _makeTarget(type(AValidator).interfaceId, payload_.validator, true)
        );
        emit BoostCreated(
            _boosts.length - 1,
            boost.owner,
            address(boost.action),
            boost.incentives.length,
            address(boost.validator),
            address(boost.allowList),
            address(boost.budget)
        );
        return boost;
    }
sherlock-admin2 commented 1 month ago

The protocol team fixed this issue in the following PRs/commits: https://github.com/boostxyz/boost-protocol/pull/197