Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Front-running allow a malicious user to manipulate transaction ordering, potentially resulting in a loss of funds for other users in `PriorityPool.sol`

Summary

This report highlights a front-running vulnerability in the staking pool contract, specifically in the depositQueuedTokens and performUpkeep functions. The vulnerability could allow a malicious user to manipulate transaction ordering, potentially resulting in a loss of funds for other users.

Vulnerability Details

The staking pool contract manages the deposit and withdrawal of tokens, as well as the upkeep of unused tokens. Two key functions—depositQueuedTokens and performUpkeep—involve the transfer of tokens and updating the staking pool strategies.

These operations are susceptible to front-running, where a malicious user can exploit the transaction ordering on the blockchain. Front-runners could monitor pending transactions, submit their own transaction with a higher gas fee, and get their transaction mined first. This manipulation of transaction order can lead to unfair profit or cause other users' transactions to fail or incur extra costs.

Affected functions:

  1. depositQueuedTokens(uint256 _queueDepositMin, uint256 _queueDepositMax, bytes[] calldata _data): This function allows for the deposit of queued tokens into staking pool strategies. If a user submits a transaction to deposit tokens, a malicious actor can front-run by depositing a large amount, preventing the user's transaction from processing as intended.

  2. performUpkeep(bytes calldata _performData): This function deposits unused tokens into the staking pool strategies and could be front-run by malicious actors trying to prioritize their own deposits.

PoC:
Scenario:
Alice and Bob both want to deposit tokens into the staking pool. Alice submits a transaction to deposit 100 tokens at T1. However, Bob, who is monitoring the mempool, notices Alice’s transaction and front-runs her by submitting a deposit for 10,000 tokens with a higher gas fee. Bob's transaction gets mined first, filling the available deposit room and leaving Alice's transaction to fail due to insufficient deposit room.

Steps for PoC:

  1. Deploy the Contract: The contract is deployed on a local Hardhat environment.

  2. Initialize Users: Alice and Bob both have tokens to deposit into the staking pool.

  3. Simulate Deposits:

    • At T1, Alice submits a transaction to deposit 100 tokens into the staking pool.

    • Bob submits a transaction at T2 (just milliseconds later), front-running Alice by offering a higher gas fee and depositing 10,000 tokens.

  4. Result: Bob’s transaction is processed first, and the available deposit room is filled by his 10,000 tokens. Alice's transaction fails due to insufficient room, despite submitting her transaction first.

Hardhat test case for front-running:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Staking Pool Front-Running PoC", function () {
let StakingPool, stakingPool, token, owner, alice, bob;
before(async function () {
// Deploy token contract and mint tokens to Alice and Bob
const Token = await ethers.getContractFactory("ERC20Mock");
token = await Token.deploy("MockToken", "MTK", 18);
await token.deployed();
// Deploy staking pool contract
StakingPool = await ethers.getContractFactory("StakingPool");
stakingPool = await StakingPool.deploy();
await stakingPool.deployed();
[owner, alice, bob] = await ethers.getSigners();
// Mint tokens to Alice and Bob
await token.mint(alice.address, ethers.utils.parseEther("1000"));
await token.mint(bob.address, ethers.utils.parseEther("10000"));
// Approve staking pool to spend Alice and Bob's tokens
await token.connect(alice).approve(stakingPool.address, ethers.utils.parseEther("100"));
await token.connect(bob).approve(stakingPool.address, ethers.utils.parseEther("10000"));
});
it("should demonstrate front-running vulnerability", async function () {
// Bob submits a high gas transaction right after Alice
await stakingPool.connect(alice).depositQueuedTokens(ethers.utils.parseEther("100"), ethers.utils.parseEther("100"), []);
// Bob front-runs with a larger deposit
await stakingPool.connect(bob).depositQueuedTokens(ethers.utils.parseEther("10000"), ethers.utils.parseEther("10000"), []);
// Check balances or contract state to confirm front-run behavior
const aliceDepositStatus = await stakingPool.getDepositStatus(alice.address);
const bobDepositStatus = await stakingPool.getDepositStatus(bob.address);
// Expect Bob's deposit to succeed and Alice's to fail
expect(aliceDepositStatus).to.equal(0); // Alice's deposit failed or was significantly reduced
expect(bobDepositStatus).to.equal(ethers.utils.parseEther("10000")); // Bob's deposit succeeded
});
});

Impact

  1. Users with smaller deposits, like Alice in the example, may see their transactions fail if they are front-run, leading to wasted gas fees and failed operations.

  2. Malicious actors with large amounts of tokens can manipulate the order of deposits, gaining an unfair advantage over other users in terms of staking returns or access to limited liquidity.

Tools Used

Manual review.

Recommendations

  1. Introduce randomness to the order in which transactions are processed, making it more difficult for malicious actors to predict and front-run specific transactions.

  2. Implement a two-phase commit-reveal mechanism for deposits. Users first commit their intent to deposit, and after a delay, reveal the actual deposit transaction. This makes it harder for attackers to front-run transactions.

  3. Use a fair ordering mechanism, such as a First-In-First-Out (FIFO) queue or even-time windows to process transactions, ensuring that deposit transactions are fairly handled.

  4. Set a cap on the gas fee for specific functions to prevent malicious actors from using high gas fees to front-run.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.