hats-finance / Circles-0x6ca9ca24d78af44582951825bef9eadcb210e5cf

Circles Protocol contracts
https://aboutcircles.com
GNU Affero General Public License v3.0
0 stars 0 forks source link

`_ensureAvatarsRegistered`is not correctly returning the `registrationCount` #70

Open hats-bug-reporter[bot] opened 2 months ago

hats-bug-reporter[bot] commented 2 months ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0x7a9e1a72521a2962d7f43acd2baed5d5ab6b214c1d0330333a25d816b5aa605c Severity: medium

Description: Description

As per migration rule, v1 circle owners can apply for migration within the bootsrap window where they no need to pay the INVITATION_COST cost. Once they pass the invitationOnlyTime, they have to pay the invitation cost which is set by the owner.

This cost apply is not correctly enforced when the migartion happen through the migration contract.

As per current implementation, the migrate function allows to be called by anyone with valid human address. it does not check whether the avatar is already migrated.

The circle are transferred from the call to the migration contract and then the migrate function is called in the Hub.sol.

In the Hub.sol, once the invitation period is passed, the invitation cost is applied and then the tokens are minted

    function migrate(address _owner, address[] calldata _avatars, uint256[] calldata _amounts) external onlyMigration {
       ---some code---
        // register all unregistered avatars as humans, and check that registered avatars are humans
        // after the bootstrap period, the _owner needs to pay the equivalent invitation cost for all newly registered humans
        uint256 cost = INVITATION_COST * _ensureAvatarsRegistered(_avatars); ---<< audit find

        // Invitation cost only applies after the bootstrap period
        if (block.timestamp > invitationOnlyTime && cost > 0) {
            // personal Circles are required to burn the invitation cost
            if (!isHuman(_owner)) {
                // Only humans can migrate v1 tokens after the bootstrap period.
                revert CirclesHubMustBeHuman(_owner, 4);
            }
            _burnAndUpdateTotalSupply(_owner, toTokenId(_owner), cost);
        }

        for (uint256 i = 0; i < _avatars.length; i++) {
            // mint the migrated balances to _owner
            _mintAndUpdateTotalSupply(_owner, toTokenId(_avatars[i]), _amounts[i], "");
        }
    }

lets look at the function _ensureAvatarsRegistered.

    function _ensureAvatarsRegistered(address[] calldata _avatars) internal returns (uint256) {
        uint256 registrationCount = 0;
        for (uint256 i = 0; i < _avatars.length; i++) {
            if (avatars[_avatars[i]] == address(0)) {
                registrationCount++;
                _registerHuman(_avatars[i]);
            } else {
                if (!isHuman(_avatars[i])) {
                    // Only humans can be registered.
                    revert CirclesHubMustBeHuman(_avatars[i], 5);
                }
            }
        }

        return registrationCount;
    }

if the avatar is already registered, it checks if it is valid human. When the avatar already exists in the avatars map, the revert will happen inside _registerHuman function call.

Note, only avatars[_avatars[i]] == address(0), the registrationCount will be updated.

Impact

  1. Revised Code File (Optional)

Inside the _ensureAvatarsRegistered, revert if avatars[_avatars[i]] != address(0)

    function _ensureAvatarsRegistered(address[] calldata _avatars) internal returns (uint256) {
        uint256 registrationCount = 0;
        for (uint256 i = 0; i < _avatars.length; i++) {
            if (avatars[_avatars[i]] == address(0)) {
                registrationCount++;
                _registerHuman(_avatars[i]);
            } else {
---               if (!isHuman(_avatars[i])) {
---                // Only humans can be registered.
                    revert CirclesHubMustBeHuman(_avatars[i], 5); -->> change the revert case.
                }
            }
        }

        return registrationCount;
    }
benjaminbollen commented 2 months ago

Thank you for your report on the _ensureAvatarsRegistered function. After review, we've determined this is not an issue.

The function is correctly implemented and performs its intended purpose. It ensures that all provided avatars are registered as humans, and returns the count of newly registered avatars. The name "ensure" indicates that the function guarantees a condition (all avatars being registered) while also providing useful information (the count of new registrations).

We appreciate your examination of our function implementations. Your attention to detail helps maintain the clarity and correctness of our codebase. Thank you for your participation in this security review.