forked from alpaca-finance/bsc-alpaca-contract
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPancakeswap_StrategyPartialCloseLiquidate.test.ts
219 lines (184 loc) · 9.08 KB
/
Pancakeswap_StrategyPartialCloseLiquidate.test.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
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import { ethers, upgrades } from "hardhat";
import { Signer } from "ethers";
import chai from "chai";
import { solidity } from "ethereum-waffle";
import "@openzeppelin/test-helpers";
import {
MockERC20,
MockERC20__factory,
PancakeFactory,
PancakeFactory__factory,
PancakePair,
PancakePair__factory,
PancakeRouter,
PancakeRouter__factory,
StrategyPartialCloseLiquidate,
StrategyPartialCloseLiquidate__factory,
WETH,
WETH__factory
} from "../typechain";
import { assertAlmostEqual } from "./helpers/assert";
chai.use(solidity);
const { expect } = chai;
describe('Pancakeswap - StrategyPartialLiquidate', () => {
const FOREVER = '2000000000';
/// Pancake-related instance(s)
let factory: PancakeFactory;
let router: PancakeRouter;
let lp: PancakePair;
/// Token-related instance(s)
let wbnb: WETH;
let baseToken: MockERC20;
let farmingToken: MockERC20;
/// Strategy-ralted instance(s)
let strat: StrategyPartialCloseLiquidate;
// Accounts
let deployer: Signer;
let alice: Signer;
let bob: Signer;
// Contract Signer
let baseTokenAsAlice: MockERC20;
let baseTokenAsBob: MockERC20;
let lpAsBob: PancakePair;
let farmingTokenAsAlice: MockERC20;
let farmingTokenAsBob: MockERC20;
let routerAsAlice: PancakeRouter;
let routerAsBob: PancakeRouter;
let stratAsBob: StrategyPartialCloseLiquidate;
beforeEach(async () => {
[deployer, alice, bob] = await ethers.getSigners();
// Setup Pancakeswap
const PancakeFactory = (await ethers.getContractFactory(
"PancakeFactory",
deployer
)) as PancakeFactory__factory;
factory = await PancakeFactory.deploy((await deployer.getAddress()));
await factory.deployed();
const WBNB = (await ethers.getContractFactory(
"WETH",
deployer
)) as WETH__factory;
wbnb = await WBNB.deploy();
await factory.deployed();
const PancakeRouter = (await ethers.getContractFactory(
"PancakeRouter",
deployer
)) as PancakeRouter__factory;
router = await PancakeRouter.deploy(factory.address, wbnb.address);
await router.deployed();
/// Setup token stuffs
const MockERC20 = (await ethers.getContractFactory(
"MockERC20",
deployer
)) as MockERC20__factory
baseToken = await upgrades.deployProxy(MockERC20, ['BTOKEN', 'BTOKEN']) as MockERC20;
await baseToken.deployed();
await baseToken.mint(await alice.getAddress(), ethers.utils.parseEther('100'));
await baseToken.mint(await bob.getAddress(), ethers.utils.parseEther('100'));
farmingToken = await upgrades.deployProxy(MockERC20, ['FTOKEN', 'FTOKEN']) as MockERC20;
await farmingToken.deployed();
await farmingToken.mint(await alice.getAddress(), ethers.utils.parseEther('10'));
await farmingToken.mint(await bob.getAddress(), ethers.utils.parseEther('10'));
await factory.createPair(baseToken.address, farmingToken.address);
lp = PancakePair__factory.connect(await factory.getPair(farmingToken.address, baseToken.address), deployer);
const StrategyPartialCloseLiquidate = (await ethers.getContractFactory(
"StrategyPartialCloseLiquidate",
deployer
)) as StrategyPartialCloseLiquidate__factory;
strat = await upgrades.deployProxy(StrategyPartialCloseLiquidate, [router.address]) as StrategyPartialCloseLiquidate;
await strat.deployed();
// Assign contract signer
baseTokenAsAlice = MockERC20__factory.connect(baseToken.address, alice);
baseTokenAsBob = MockERC20__factory.connect(baseToken.address, bob);
farmingTokenAsAlice = MockERC20__factory.connect(farmingToken.address, alice);
farmingTokenAsBob = MockERC20__factory.connect(farmingToken.address, bob);
routerAsAlice = PancakeRouter__factory.connect(router.address, alice);
routerAsBob = PancakeRouter__factory.connect(router.address, bob);
lpAsBob = PancakePair__factory.connect(lp.address, bob);
stratAsBob = StrategyPartialCloseLiquidate__factory.connect(strat.address, bob);
// Setting up liquidity
// Alice adds 0.1 FTOKEN + 1 BTOKEN
await baseTokenAsAlice.approve(router.address, ethers.utils.parseEther('1'));
await farmingTokenAsAlice.approve(router.address, ethers.utils.parseEther('0.1'));
await routerAsAlice.addLiquidity(
baseToken.address, farmingToken.address,
ethers.utils.parseEther('1'), ethers.utils.parseEther('0.1'), '0', '0',
await alice.getAddress(), FOREVER);
// Bob tries to add 1 FTOKEN + 1 BTOKEN (but obviously can only add 0.1 FTOKEN)
await baseTokenAsBob.approve(router.address, ethers.utils.parseEther('1'));
await farmingTokenAsBob.approve(router.address, ethers.utils.parseEther('1'));
await routerAsBob.addLiquidity(
baseToken.address, farmingToken.address,
ethers.utils.parseEther('1'), ethers.utils.parseEther('1'), '0', '0',
await bob.getAddress(), FOREVER);
expect(await baseToken.balanceOf(await bob.getAddress())).to.be.bignumber.eq(ethers.utils.parseEther('99'));
expect(await farmingToken.balanceOf(await bob.getAddress())).to.be.bignumber.eq(ethers.utils.parseEther('9.9'));
expect(await lp.balanceOf(await bob.getAddress())).to.be.bignumber.eq(ethers.utils.parseEther('0.316227766016837933'));
});
it('should revert on bad calldata', async () => {
// Bob passes some bad calldata that can't be decoded
await expect(
stratAsBob.execute(await bob.getAddress(), '0', '0x1234')
).to.be.reverted;
});
it('should revert when the given LPs > the actual LPs sent to strategy', async () => {
// Bob transfer LP to strategy first
await lpAsBob.transfer(strat.address, ethers.utils.parseEther('0.316227766016837933'));
// Bob uses partial close liquidate strategy to with unrealistic returnLp to liquidate
// Bob only transfer ~0.316227766016837933 LPs. However, he ask to liquidate 1000 LPs
await expect(stratAsBob.execute(await bob.getAddress(), '0',
ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256'],
[baseToken.address, farmingToken.address, ethers.utils.parseEther('1000'), ethers.utils.parseEther('0.5')]
)
)).revertedWith('StrategyPartialCloseLiquidate::execute:: insufficient LP amount recevied from worker');
});
it('should revert when the give LPs are liquidated but slippage > minBaseToken', async () => {
// Bob transfer LP to strategy first
const bobLpBefore = await lp.balanceOf(await bob.getAddress());
await lpAsBob.transfer(strat.address, ethers.utils.parseEther('0.316227766016837933'));
// Bob uses partial close liquidate strategy to turn the 50% LPs back to BTOKEN with invalid minBaseToken
// 50% LPs is worth aroud ~0.87 BTOKEN. Bob enters minBaseToken 1000 BTOKEN which will obviously make tx fail.
const returnLp = bobLpBefore.div(2)
await expect(stratAsBob.execute(await bob.getAddress(), '0',
ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256'],
[baseToken.address, farmingToken.address, returnLp, ethers.utils.parseEther('1000')]
)
)).revertedWith('StrategyPartialCloseLiquidate::execute:: insufficient baseToken received');
});
it('should convert the given LP tokens back to baseToken, when maxReturn >= liquidated amount', async () => {
// Bob transfer LP to strategy first
const bobLpBefore = await lp.balanceOf(await bob.getAddress());
const bobBTokenBefore = await baseToken.balanceOf(await bob.getAddress());
await lpAsBob.transfer(strat.address, ethers.utils.parseEther('0.316227766016837933'));
// Bob uses partial close liquidate strategy to turn the 50% LPs back to BTOKEN with the same minimum value and the same maxReturn
const returnLp = bobLpBefore.div(2)
await stratAsBob.execute(await bob.getAddress(), '0',
ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'uint256'],
[baseToken.address, farmingToken.address, returnLp, ethers.utils.parseEther('0.5')]
)
);
// After execute strategy successfully. The following conditions must be statified
// - LPs in Strategy contract must be 0
// - Bob should have bobLpBefore - returnLp left in his account
// - Bob should have bobBtokenBefore + 0.5 BTOKEN + [((0.05*998)*1.5)/(0.15*1000+(0.05*998)) = ~0.374437218609304645 BTOKEN] (from swap 0.05 FTOKEN to BTOKEN) in his account
// - BTOKEN in reserve should be 1.5-0.374437218609304645 = 1.12556278 BTOKEN
// - FTOKEN in reserve should be 0.15+(0.05*0.998) = 0.2 FTOKEN
expect(await lp.balanceOf(strat.address)).to.be.bignumber.eq(ethers.utils.parseEther('0'));
expect(await lp.balanceOf(await bob.getAddress())).to.be.bignumber.eq(bobLpBefore.sub(returnLp));
assertAlmostEqual(
bobBTokenBefore.add(ethers.utils.parseEther('0.5')).add(ethers.utils.parseEther('0.374437218609304645')).toString(),
(await baseToken.balanceOf(await bob.getAddress())).toString()
);
assertAlmostEqual(
ethers.utils.parseEther('1.12556278').toString(),
(await baseToken.balanceOf(lp.address)).toString()
);
assertAlmostEqual(
ethers.utils.parseEther('0.2').toString(),
(await farmingToken.balanceOf(lp.address)).toString()
);
});
});