Skip to content

Latest commit

 

History

History
93 lines (64 loc) · 3.72 KB

File metadata and controls

93 lines (64 loc) · 3.72 KB

Docile Cerulean Zebra

Medium

If ERC20VariableIncentive and ERC20Incentive get disbursed multiple times, additional funds may get locked forever

Summary

The ERC20VariableIncentive can be disbursed multiple times, but when this happens, the extra disbursed amount will be locked in contract forever.

Root Cause

ManagedBudget::disburse allows budget owners to provide assets/liquidity for incentives, this function is called when incentives are being deployed, but owners can also call it later if they want to put more into incentives, the function does a simple job, to transfer correspond types of assets to the target address, in this case, the incentive contracts.

    function disburse(bytes calldata data_)
        public
        virtual
        override
        onlyOwnerOrRoles(ADMIN_ROLE | MANAGER_ROLE)
        returns (bool)
    {
        Transfer memory request = abi.decode(data_, (Transfer));
        if (request.assetType == AssetType.ERC20 || request.assetType == AssetType.ETH) {
            FungiblePayload memory payload = abi.decode(request.data, (FungiblePayload));

            uint256 avail = available(request.asset);
            if (payload.amount > avail) {
                revert InsufficientFunds(request.asset, avail, payload.amount);
            }

            _transferFungible(request.asset, request.target, payload.amount);
        } else if (request.assetType == AssetType.ERC1155) {
            ERC1155Payload memory payload = abi.decode(request.data, (ERC1155Payload));

            uint256 avail = IERC1155(request.asset).balanceOf(address(this), payload.tokenId);
            if (payload.amount > avail) {
                revert InsufficientFunds(request.asset, avail, payload.amount);
            }

            _transferERC1155(request.asset, request.target, payload.tokenId, payload.amount, payload.data);
        } else {
            return false;
        }

        return true;
    }

In ERC20VariableIncentive contract, when it gets clawed back, limit will be deducted by the withdrawn amount:

    function clawback(bytes calldata data_) external override onlyOwner returns (bool) {
        ClawbackPayload memory claim_ = abi.decode(data_, (ClawbackPayload));
        (uint256 amount) = abi.decode(claim_.data, (uint256));

        limit -= amount;

        // Transfer the tokens back to the intended recipient
        asset.safeTransfer(claim_.target, amount);
        emit Claimed(claim_.target, abi.encodePacked(asset, claim_.target, amount));

        return true;
    }

As limit is a fixed constant, so when more assets are disbursed to this incentive, they will not be distributed, and when it exceeds certain amount, those extra ones cannot be clawed back either, as limit will go as low as zero, and anything beyond that will make clawback revert.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

Additional assets disbursed will be locked forever.

PoC

  1. Boost owner sets up a boost, and disbursed 1000 USDC to the ERC20VariableIncentive incentive, and sets limit to 1000 as well.
  2. After 500 USDC has been claimed, the budget admin/owner wants to add additional 2000 USDC to the incentive.
  3. At most 500 more USDC can be claimed, as more would exceeds limit set earlier.
  4. Budget owner tries to call clawback to get the extra USDC back, but they can only get 1000 back, as the limit is 1000, the rest 1000 will be locked forever.

Mitigation

For incentives, add an entry for more disburse, and sets the value or revert the call accordingly.