Althea Liquid Infrastructure - pontifex's results

Liquid Infrastructure.

General Information

Platform: Code4rena

Start Date: 13/02/2024

Pot Size: $24,500 USDC

Total HM: 5

Participants: 84

Period: 6 days

Judge: 0xA5DF

Id: 331

League: ETH

Althea

Findings Distribution

Researcher Performance

Rank: 63/84

Findings: 1

Award: $7.18

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L142-L145

Vulnerability details

Impact

The issue can be exploited to receive additional amounts of the distributed assets and/or to disrupt the core functionality of the protocol.

Proof of Concept

The LiquidInfrastructureERC20 contract inherits Open Zeppelin's ERC20 implementation. So it is possible to transfer zero values. There is additional restriction in the LiquidInfrastructureERC20._beforeTokenTransfer huk that the receiver should be approved to hold the token:

            require(
                isApprovedHolder(to),
                "receiver not approved to hold the token"
            );

In case the receiver's balanceOf is zero the receiver's address is pushed to the holders array:

        bool exists = (this.balanceOf(to) != 0);
        if (!exists) {
            holders.push(to);
        }

So if someone sends zero value to an approved address with zero balance the address is pushed to the holders array. Since the address balance is still zero the operation can be repeated again.

The holders array is used in the distribute function to distribute ERC20s tokens among the protocol's tokens holders:

        for (i = nextDistributionRecipient; i < limit; i++) {
            address recipient = holders[i];
            if (isApprovedHolder(recipient)) {
                uint256[] memory receipts = new uint256[](
                    distributableERC20s.length
                );
                for (uint j = 0; j < distributableERC20s.length; j++) {
                    IERC20 toDistribute = IERC20(distributableERC20s[j]);
                    uint256 entitlement = erc20EntitlementPerUnit[j] *
                        this.balanceOf(recipient);
                    if (toDistribute.transfer(recipient, entitlement)) {
                        receipts[j] = entitlement;
                    }
                }

                emit Distribution(recipient, distributableERC20s, receipts);
            }
        }                

Several includings at the holders array lets the holder receive the additional amount of assets in case of sufficient assets balance. When the assets balance of the contract becomes insufficient (because of transferring additional amounts) the distribute function throws an error and the core functionality of the protocol is blocked by the LockedForDistribution status. It can only be changed at the distribute function by calling the _endDistribution:

        nextDistributionRecipient = i;

        if (nextDistributionRecipient == holders.length) {
            _endDistribution();
        }

Then only the contract owner can solve the accident by removing tokens from distributableERC20s array or holders from the HolderAllowlist mapping.

Tools Used

Manual review

Consider pushing addresses at the holders array only when amount is non zero:

        bool exists = (this.balanceOf(to) != 0);
-       if (!exists) {
+       if (!exists && amount != 0) {
            holders.push(to);
        }

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-02-20T06:24:17Z

0xRobocop marked the issue as duplicate of #536

#1 - c4-pre-sort

2024-02-20T06:33:41Z

0xRobocop marked the issue as duplicate of #77

#2 - c4-judge

2024-03-03T13:30:16Z

0xA5DF marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter