hats-finance / Intuition-0x538dbadc50cc87b281cd655f1edbc6ebda02a66a

The smart contracts of the Intuition protocol v1.
https://intuition.systems
Other
0 stars 1 forks source link

Unaccounted ETH Surplus in batchCreateAtom Function Leading to Locked Funds #69

Open hats-bug-reporter[bot] opened 3 days ago

hats-bug-reporter[bot] commented 3 days ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0x766ad11162ca820ffb4609089bf9a36dbec3919fcaada77d1760298d67719d45 Severity: high

Description: Description\ In the batchCreateAtom function of the EthMultiVault contract, there's a potential for loss of user funds due to imprecise division when calculating valuePerAtom. This occurs when the msg.value is not perfectly divisible by the number of atoms being created. The surplus amount is not accounted for and effectively becomes locked in the contract, inaccessible to both the user and the protocol. https://github.com/hats-finance/Intuition-0x538dbadc50cc87b281cd655f1edbc6ebda02a66a/blob/b2e422ff0c3e3729e58d2699fdf2ef8699fbd172/src/EthMultiVault.sol#L412-L440

Attack Scenario\

  1. A user calls batchCreateAtom to create 3 atoms, sending 119 wei as the msg.value.
  2. The function calculates valuePerAtom as 119 / 20 = 5 wei.
  3. Each atom is created with 5 wei, using a total of 100 wei.
  4. The remaining 19 wei are left in the contract, not returned to the user or allocated to any specific purpose.
  5. This process can be repeated, gradually accumulating small amounts of ETH in the contract that cannot be recovered.

Attachments

  1. Proof of Concept (PoC) File
    
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;

import "./EthMultiVault.sol";

contract EthMultiVaultExploit { EthMultiVault public vault;

constructor(address _vaultAddress) {
    vault = EthMultiVault(_vaultAddress);
}

function exploitSurplus() external payable {
    require(msg.value == 119, "Send exactly 119 wei");

    bytes[] memory atomUris = new bytes[](3);
    atomUris[0] = "uri1";
    atomUris[1] = "uri2";
    atomUris[2] = "uri3";

    vault.batchCreateAtom{value: 119}(atomUris);

    // At this point, 2 wei are locked in the contract
}

}


2. **Revised Code File (Optional)**
```solidity
  function batchCreateAtom(bytes[] calldata atomUris)
        external
        payable
        nonReentrant
        whenNotPaused
        returns (uint256[] memory)
    {
        uint256 length = atomUris.length;
        if (msg.value < getAtomCost() * length) {
            revert Errors.MultiVault_InsufficientBalance();
        }

        uint256 valuePerAtom = msg.value / length;
        uint256 protocolDepositFeeTotal;
        uint256[] memory ids = new uint256[](length);

        for (uint256 i = 0; i < length; i++) {
            uint256 protocolDepositFee;
            (ids[i], protocolDepositFee) = _createAtom(atomUris[i], valuePerAtom);

            // add protocol deposit fees to total
            protocolDepositFeeTotal += protocolDepositFee;
        }

        uint256 totalFeesForProtocol = atomConfig.atomCreationProtocolFee * length + protocolDepositFeeTotal;
        _transferFeesToProtocolVault(totalFeesForProtocol);

        // Calculate and refund any surplus
        uint256 surplus = msg.value - (valuePerAtom * length);
        if (surplus > 0) {
            payable(msg.sender).transfer(surplus);
            //optional add WETH fallback send
        }

        return ids;
    }