From 6133a04861d3fbe9b2d79390de6293ba23f58d02 Mon Sep 17 00:00:00 2001 From: Pierrick Turelier Date: Fri, 3 Jun 2022 11:36:55 -0500 Subject: [PATCH] feat(TwabRewards): add exploit scenario to fork --- scripts/fork/distribute.ts | 4 +- scripts/fork/twabRewards.ts | 219 ++++++++++++++++++++++++------------ 2 files changed, 152 insertions(+), 71 deletions(-) diff --git a/scripts/fork/distribute.ts b/scripts/fork/distribute.ts index 12d580d..3b946f2 100644 --- a/scripts/fork/distribute.ts +++ b/scripts/fork/distribute.ts @@ -17,7 +17,7 @@ export default task("fork:distribute", "Distribute Ether and USDC").setAction( const { ethers } = hre; const { provider, getContractAt, getSigners } = ethers; - const [deployer, wallet2] = await getSigners(); + const [deployer, attacker] = await getSigners(); const ethHolder = provider.getUncheckedSigner(ETH_HOLDER_ADDRESS_MAINNET); const poolHolder = provider.getUncheckedSigner(POOL_HOLDER_ADDRESS_MAINNET); @@ -28,7 +28,7 @@ export default task("fork:distribute", "Distribute Ether and USDC").setAction( const recipients: { [key: string]: string } = { ["Deployer"]: deployer.address, - ["Wallet 2"]: wallet2.address, + ["Attacker"]: attacker.address, }; const keys = Object.keys(recipients); diff --git a/scripts/fork/twabRewards.ts b/scripts/fork/twabRewards.ts index 109c198..ddde8e2 100644 --- a/scripts/fork/twabRewards.ts +++ b/scripts/fork/twabRewards.ts @@ -1,29 +1,23 @@ -import { getContractFactory } from "@nomiclabs/hardhat-ethers/types"; import { usdc } from "@studydefi/money-legos/erc20"; import { subtask, task, types } from "hardhat/config"; -import { - POOL_TOKEN_ADDRESS_MAINNET, - POOL_TOKEN_DECIMALS, -} from "../../Constants"; +import { POOL_TOKEN_ADDRESS_MAINNET, POOL_TOKEN_DECIMALS } from "../../Constants"; import { action, info, success } from "../../helpers"; -import { - increaseTime as increaseTimeUtil, -} from "../../test/utils/increaseTime"; +import { increaseTime as increaseTimeUtil } from "../../test/utils/increaseTime"; export default task("fork:twab-rewards", "Run TWAB Rewards fork").setAction( async (taskArguments, hre) => { action("Run TWAB Rewards fork..."); - const { ethers, getNamedAccounts, run } = hre; + const { ethers, run } = hre; - const { getContractAt, provider, utils } = ethers; - const { deployer } = await getNamedAccounts(); + const { getContractAt, getSigners, provider } = ethers; + const [deployer, attacker] = await getSigners(); const increaseTime = (time: number) => increaseTimeUtil(provider, time); - info(`Deployer is: ${deployer}`); + info(`Deployer is: ${deployer.address}`); const prizePoolAddress = await run("fork:create-pool"); @@ -31,15 +25,52 @@ export default task("fork:twab-rewards", "Run TWAB Rewards fork").setAction( const ticketAddress = await prizePool.getTicket(); const twabRewardsAddress = await run("deploy-twab-rewards", { ticketAddress }); - const promotionId = (await run("create-promotion", { twabRewardsAddress })).toNumber(); + + const firstPromotionId = ( + await run("create-promotion", { + twabRewardsAddress, + signer: deployer, + tokensPerEpoch: "1000", + epochDuration: 604800, + numberOfEpochs: 12, + }) + ).toNumber(); + + const secondPromotionId = ( + await run("create-promotion", { + twabRewardsAddress, + signer: attacker, + tokensPerEpoch: "12000", + epochDuration: 60, + numberOfEpochs: 1, + }) + ).toNumber(); await run("deposit-into-prize-pool", { prizePoolAddress }); + // Attacker ends promotion and receives back is initial deposit + await run("end-promotion", { + twabRewardsAddress, + promotionId: secondPromotionId, + signer: attacker, + }); + // We move time 6 months forward await increaseTime(15778458); - await run("claim-rewards", { twabRewardsAddress, promotionId }); - await run("destroy-promotion", { twabRewardsAddress, promotionId }); + // Attacker should not be able to steal funds from first promotion + await run("destroy-promotion", { + twabRewardsAddress, + promotionId: secondPromotionId, + signer: attacker, + }); + + await run("claim-rewards", { twabRewardsAddress, promotionId: firstPromotionId }); + await run("destroy-promotion", { + twabRewardsAddress, + promotionId: firstPromotionId, + signer: deployer, + }); } ); @@ -67,45 +98,58 @@ subtask("deploy-twab-rewards", "Deploy TWAB Rewards") subtask("create-promotion", "Create TWAB Rewards promotion") .addParam("twabRewardsAddress", "TWAB Rewards address") - .setAction(async ({ twabRewardsAddress }, { ethers }) => { - action("Create TWAB Rewards promotion..."); - - const { getContractAt, getSigners, provider, utils } = ethers; - const { getTransactionReceipt } = provider; - const { parseUnits } = utils; - - const [deployer] = await getSigners(); - const twabRewards = await getContractAt("TwabRewards", twabRewardsAddress); - const poolContract = await getContractAt(usdc.abi, POOL_TOKEN_ADDRESS_MAINNET, deployer); - - await poolContract.approve(twabRewardsAddress, parseUnits("12000", POOL_TOKEN_DECIMALS)); - - const createPromotionTx = await twabRewards.createPromotion( - POOL_TOKEN_ADDRESS_MAINNET, - (await provider.getBlock('latest')).timestamp, - parseUnits("1000", POOL_TOKEN_DECIMALS), - 604800, - 12 - ); - - const createPromotionTxReceipt = await getTransactionReceipt(createPromotionTx.hash); - - const createPromotionTxEvents = createPromotionTxReceipt.logs.map((log: any) => { - try { - return twabRewards.interface.parseLog(log); - } catch (e) { - return null; - } - }); - - const promotionCreatedEvent = createPromotionTxEvents.find( - (event: any) => event && event.name === "PromotionCreated" - ); - - success("TWAB Rewards promotion created!"); - - return promotionCreatedEvent?.args['promotionId']; - }); + .addParam("signer", "Transaction signer", null, types.any) + .addParam("tokensPerEpoch", "Number of tokens per epoch") + .addParam("epochDuration", "Duration of an epoch in seconds", null, types.float) + .addParam("numberOfEpochs", "Number of epochs", null, types.float) + .setAction( + async ( + { twabRewardsAddress, signer, tokensPerEpoch, epochDuration, numberOfEpochs }, + { ethers } + ) => { + action("Create TWAB Rewards promotion..."); + + const { getContractAt, provider, utils } = ethers; + const { getTransactionReceipt } = provider; + const { parseUnits } = utils; + + const twabRewards = await getContractAt("TwabRewards", twabRewardsAddress, signer); + const poolContract = await getContractAt(usdc.abi, POOL_TOKEN_ADDRESS_MAINNET, signer); + + await poolContract.approve( + twabRewardsAddress, + parseUnits("12000", POOL_TOKEN_DECIMALS) + ); + + const createPromotionTx = await twabRewards.createPromotion( + POOL_TOKEN_ADDRESS_MAINNET, + ( + await provider.getBlock("latest") + ).timestamp, + parseUnits(tokensPerEpoch, POOL_TOKEN_DECIMALS), + epochDuration, + numberOfEpochs + ); + + const createPromotionTxReceipt = await getTransactionReceipt(createPromotionTx.hash); + + const createPromotionTxEvents = createPromotionTxReceipt.logs.map((log: any) => { + try { + return twabRewards.interface.parseLog(log); + } catch (e) { + return null; + } + }); + + const promotionCreatedEvent = createPromotionTxEvents.find( + (event: any) => event && event.name === "PromotionCreated" + ); + + success("TWAB Rewards promotion created!"); + + return promotionCreatedEvent?.args["promotionId"]; + } + ); subtask("deposit-into-prize-pool", "Deposit into prize pool") .addParam("prizePoolAddress", "Prize pool address") @@ -115,7 +159,7 @@ subtask("deposit-into-prize-pool", "Deposit into prize pool") const { getContractAt, getSigners, utils } = ethers; const { parseUnits } = utils; - const [deployer, wallet2] = await getSigners(); + const [deployer, attacker] = await getSigners(); const prizePool = await getContractAt("YieldSourcePrizePool", prizePoolAddress); const usdcContract = await getContractAt(usdc.abi, usdc.address); @@ -123,10 +167,15 @@ subtask("deposit-into-prize-pool", "Deposit into prize pool") await usdcContract.connect(deployer).approve(prizePoolAddress, depositAmountDeployer); const depositAmountWallet2 = parseUnits("250", usdc.decimals); - await usdcContract.connect(wallet2).approve(prizePoolAddress, depositAmountWallet2); + await usdcContract.connect(attacker).approve(prizePoolAddress, depositAmountWallet2); - await prizePool.connect(deployer).depositToAndDelegate(deployer.address, depositAmountDeployer, deployer.address); - await prizePool.connect(wallet2).depositToAndDelegate(wallet2.address, depositAmountWallet2, wallet2.address); + await prizePool + .connect(deployer) + .depositToAndDelegate(deployer.address, depositAmountDeployer, deployer.address); + + await prizePool + .connect(attacker) + .depositToAndDelegate(attacker.address, depositAmountWallet2, attacker.address); success("Successfully deposited into the prize pool!"); }); @@ -164,7 +213,7 @@ subtask("claim-rewards", "Claim rewards") (event: any) => event && event.name === "RewardsClaimed" ); - const rewardsClaimedAmount = formatEther(rewardsClaimedEvent?.args['amount']); + const rewardsClaimedAmount = formatEther(rewardsClaimedEvent?.args["amount"]); success(`Successfully claimed ${rewardsClaimedAmount} POOL!`); }); @@ -172,20 +221,17 @@ subtask("claim-rewards", "Claim rewards") subtask("destroy-promotion", "Destroy promotion") .addParam("twabRewardsAddress", "TWAB Rewards address") .addParam("promotionId", "Id of the promotion", null, types.float) - .setAction(async ({ twabRewardsAddress, promotionId }, { ethers }) => { - action("Claim rewards..."); + .addParam("signer", "Transaction signer", null, types.any) + .setAction(async ({ twabRewardsAddress, promotionId, signer }, { ethers }) => { + action("Destroy promotion..."); const { getContractAt, getSigners, provider, utils } = ethers; const { getTransactionReceipt } = provider; const { formatEther } = utils; - const [deployer] = await getSigners(); - const twabRewards = await getContractAt("TwabRewards", twabRewardsAddress, deployer); + const twabRewards = await getContractAt("TwabRewards", twabRewardsAddress, signer); - const destroyPromotionTx = await twabRewards.destroyPromotion( - promotionId, - deployer.address, - ); + const destroyPromotionTx = await twabRewards.destroyPromotion(promotionId, signer.address); const destroyPromotionReceipt = await getTransactionReceipt(destroyPromotionTx.hash); @@ -201,7 +247,42 @@ subtask("destroy-promotion", "Destroy promotion") (event: any) => event && event.name === "PromotionDestroyed" ); - const promotionDestroyedAmount = formatEther(promotionDestroyedEvent?.args['amount']); + const promotionDestroyedAmount = formatEther(promotionDestroyedEvent?.args["amount"]); + + success( + `Successfully destroyed promotion and received ${promotionDestroyedAmount} POOL back!` + ); + }); + +subtask("end-promotion", "End promotion") + .addParam("twabRewardsAddress", "TWAB Rewards address") + .addParam("promotionId", "Id of the promotion", null, types.float) + .addParam("signer", "Transaction signer", null, types.any) + .setAction(async ({ twabRewardsAddress, promotionId, signer }, { ethers }) => { + action("End promotion..."); + + const { getContractAt, provider, utils } = ethers; + const { getTransactionReceipt } = provider; + const { formatEther } = utils; + + const twabRewards = await getContractAt("TwabRewards", twabRewardsAddress, signer); + + const endPromotionTx = await twabRewards.endPromotion(promotionId, signer.address); + const endPromotionReceipt = await getTransactionReceipt(endPromotionTx.hash); + + const endPromotionEvents = endPromotionReceipt.logs.map((log: any) => { + try { + return twabRewards.interface.parseLog(log); + } catch (e) { + return null; + } + }); + + const promotionEndedEvent = endPromotionEvents.find( + (event: any) => event && event.name === "PromotionEnded" + ); + + const promotionEndedAmount = formatEther(promotionEndedEvent?.args["amount"]); - success(`Successfully destroyed promotion and received ${promotionDestroyedAmount} POOL back!`); + success(`Successfully ended promotion and received ${promotionEndedAmount} POOL back!`); });