Skip to content

Commit b2b1d1b

Browse files
committed
Split IssueUSDModule.test.ts
1 parent 44ed5b2 commit b2b1d1b

7 files changed

+961
-674
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import assertBn from '@synthetixio/core-utils/utils/assertions/assert-bignumber';
2+
import assertRevert from '@synthetixio/core-utils/utils/assertions/assert-revert';
3+
import assertEvent from '@synthetixio/core-utils/utils/assertions/assert-event';
4+
import { snapshotCheckpoint } from '@synthetixio/core-utils/utils/mocha/snapshot';
5+
import { BigNumber, constants, ethers } from 'ethers';
6+
import hre from 'hardhat';
7+
import { bn, bootstrapWithStakedPool } from '../../bootstrap';
8+
import { verifyUsesFeatureFlag } from '../../verifications';
9+
10+
const MARKET_FEATURE_FLAG = ethers.utils.formatBytes32String('registerMarket');
11+
12+
describe('IssueUSDModule', function () {
13+
const { signers, systems, provider, accountId, poolId, depositAmount, collateralAddress } =
14+
bootstrapWithStakedPool();
15+
16+
let owner: ethers.Signer, user1: ethers.Signer, user2: ethers.Signer;
17+
18+
let MockMarket: ethers.Contract;
19+
let marketId: BigNumber;
20+
21+
const feeAddress = '0x1234567890123456789012345678901234567890';
22+
23+
before('identify signers', async () => {
24+
[owner, user1, user2] = signers();
25+
});
26+
27+
before('deploy and connect fake market', async () => {
28+
const factory = await hre.ethers.getContractFactory('MockMarket');
29+
30+
MockMarket = await factory.connect(owner).deploy();
31+
32+
await systems()
33+
.Core.connect(owner)
34+
.addToFeatureFlagAllowlist(MARKET_FEATURE_FLAG, await user1.getAddress());
35+
36+
marketId = await systems().Core.connect(user1).callStatic.registerMarket(MockMarket.address);
37+
38+
await systems().Core.connect(user1).registerMarket(MockMarket.address);
39+
40+
await MockMarket.connect(owner).initialize(
41+
systems().Core.address,
42+
marketId,
43+
ethers.utils.parseEther('1')
44+
);
45+
46+
await systems()
47+
.Core.connect(owner)
48+
.setPoolConfiguration(poolId, [
49+
{
50+
marketId: marketId,
51+
weightD18: ethers.utils.parseEther('1'),
52+
maxDebtShareValueD18: ethers.utils.parseEther('10000000000000000'),
53+
},
54+
]);
55+
56+
await systems()
57+
.Core.connect(owner)
58+
.configureCollateral({
59+
tokenAddress: await systems().Core.getUsdToken(),
60+
oracleNodeId: ethers.utils.formatBytes32String(''),
61+
issuanceRatioD18: bn(150),
62+
liquidationRatioD18: bn(100),
63+
liquidationRewardD18: 0,
64+
minDelegationD18: 0,
65+
depositingEnabled: true,
66+
});
67+
});
68+
69+
const restore = snapshotCheckpoint(provider);
70+
71+
// eslint-disable-next-line max-params
72+
function verifyAccountState(
73+
accountId: number,
74+
poolId: number,
75+
collateralAmount: ethers.BigNumberish,
76+
debt: ethers.BigNumberish
77+
) {
78+
return async () => {
79+
assertBn.equal(
80+
await systems().Core.getPositionCollateral(accountId, poolId, collateralAddress()),
81+
collateralAmount
82+
);
83+
assertBn.equal(
84+
await systems().Core.callStatic.getPositionDebt(accountId, poolId, collateralAddress()),
85+
debt
86+
);
87+
};
88+
}
89+
90+
describe('burnUSD()', () => {
91+
before(restore);
92+
before('mint', async () => {
93+
await systems()
94+
.Core.connect(user1)
95+
.mintUsd(accountId, poolId, collateralAddress(), depositAmount.div(10));
96+
await systems()
97+
.Core.connect(user1)
98+
.withdraw(accountId, await systems().Core.getUsdToken(), depositAmount.div(10));
99+
});
100+
101+
const restoreBurn = snapshotCheckpoint(provider);
102+
103+
verifyUsesFeatureFlag(
104+
() => systems().Core,
105+
'burnUsd',
106+
() =>
107+
systems()
108+
.Core.connect(user1)
109+
.burnUsd(accountId, poolId, collateralAddress(), depositAmount.div(10))
110+
);
111+
112+
describe('burn from other account', async () => {
113+
before(restoreBurn);
114+
before('transfer burn collateral', async () => {
115+
// send the collateral to account 2 so it can burn on behalf
116+
await systems()
117+
.USD.connect(user1)
118+
.transfer(await user2.getAddress(), depositAmount.div(10));
119+
});
120+
121+
before('user deposit into other account', async () => {
122+
await systems()
123+
.USD.connect(user2)
124+
.approve(systems().Core.address, constants.MaxUint256.toString());
125+
await systems()
126+
.Core.connect(user2)
127+
.deposit(accountId, await systems().Core.getUsdToken(), depositAmount.div(10));
128+
});
129+
130+
it('other account burn would revert', async () => {
131+
await assertRevert(
132+
systems()
133+
.Core.connect(user2)
134+
.burnUsd(accountId, poolId, collateralAddress(), depositAmount.div(10)),
135+
'PermissionDenied'
136+
);
137+
});
138+
139+
it(
140+
'has correct debt',
141+
verifyAccountState(accountId, poolId, depositAmount, depositAmount.div(10))
142+
);
143+
144+
it('did not took away from user2 balance', async () => {
145+
assertBn.equal(await systems().USD.balanceOf(await user2.getAddress()), 0);
146+
});
147+
});
148+
149+
describe('successful partial burn when fee is levied', async () => {
150+
before(restoreBurn);
151+
before('set fee', async () => {
152+
await systems()
153+
.Core.connect(owner)
154+
.setConfig(
155+
ethers.utils.formatBytes32String('burnUsd_feeRatio'),
156+
ethers.utils.hexZeroPad(ethers.utils.parseEther('0.01').toHexString(), 32)
157+
); // 1% fee levy
158+
await systems()
159+
.Core.connect(owner)
160+
.setConfig(
161+
ethers.utils.formatBytes32String('burnUsd_feeAddress'),
162+
ethers.utils.hexZeroPad(feeAddress, 32)
163+
);
164+
});
165+
166+
before('account partial burn debt', async () => {
167+
await systems()
168+
.USD.connect(user1)
169+
.approve(systems().Core.address, constants.MaxUint256.toString());
170+
171+
await systems()
172+
.Core.connect(user1)
173+
.deposit(
174+
accountId,
175+
await systems().Core.getUsdToken(),
176+
depositAmount.div(20).add(depositAmount.div(2000))
177+
);
178+
179+
// in order to burn all with the fee we need a bit more
180+
await systems()
181+
.Core.connect(user1)
182+
.burnUsd(
183+
accountId,
184+
poolId,
185+
collateralAddress(),
186+
depositAmount.div(20).add(depositAmount.div(2000))
187+
); // pay off 50.5
188+
});
189+
190+
it(
191+
'has correct debt',
192+
verifyAccountState(accountId, poolId, depositAmount, depositAmount.div(20))
193+
);
194+
195+
it('took away from user1', async () => {
196+
assertBn.equal(
197+
await systems().USD.balanceOf(await user1.getAddress()),
198+
ethers.utils.parseEther('49.5')
199+
);
200+
});
201+
202+
it('sent money to the fee address', async () => {
203+
assertBn.equal(await systems().USD.balanceOf(feeAddress), depositAmount.div(2000));
204+
});
205+
});
206+
207+
describe('successful max burn when fee is levied', async () => {
208+
before(restoreBurn);
209+
210+
before('acquire additional balance to pay off fee', async () => {
211+
await systems()
212+
.Core.connect(user1)
213+
.mintUsd(accountId, 0, collateralAddress(), depositAmount.div(1000));
214+
});
215+
216+
before('set fee', async () => {
217+
await systems()
218+
.Core.connect(owner)
219+
.setConfig(
220+
ethers.utils.formatBytes32String('burnUsd_feeRatio'),
221+
ethers.utils.hexZeroPad(ethers.utils.parseEther('0.01').toHexString(), 32)
222+
); // 1% fee levy
223+
await systems()
224+
.Core.connect(owner)
225+
.setConfig(
226+
ethers.utils.formatBytes32String('burnUsd_feeAddress'),
227+
ethers.utils.hexZeroPad(feeAddress, 32)
228+
);
229+
});
230+
231+
let tx: ethers.providers.TransactionResponse;
232+
233+
before('account partial burn debt', async () => {
234+
// in order to burn all with the fee we need a bit more
235+
await systems()
236+
.Core.connect(user1)
237+
.withdraw(accountId, await systems().Core.getUsdToken(), depositAmount.div(1000));
238+
239+
await systems()
240+
.USD.connect(user1)
241+
.approve(systems().Core.address, constants.MaxUint256.toString());
242+
243+
await systems()
244+
.Core.connect(user1)
245+
.deposit(
246+
accountId,
247+
await systems().Core.getUsdToken(),
248+
await systems().USD.balanceOf(await user1.getAddress())
249+
);
250+
251+
tx = await systems()
252+
.Core.connect(user1)
253+
.burnUsd(accountId, poolId, collateralAddress(), depositAmount); // pay off everything
254+
});
255+
256+
it('has correct debt', verifyAccountState(accountId, poolId, depositAmount, 0));
257+
258+
it('took away from user1', async () => {
259+
assertBn.equal(await systems().USD.balanceOf(await user1.getAddress()), 0);
260+
});
261+
262+
it('sent money to the fee address', async () => {
263+
assertBn.equal(await systems().USD.balanceOf(feeAddress), depositAmount.div(1000));
264+
});
265+
266+
it('emitted event', async () => {
267+
await assertEvent(
268+
tx,
269+
`IssuanceFeePaid(${accountId}, ${poolId}, "${collateralAddress()}", ${depositAmount.div(
270+
1000
271+
)})`,
272+
systems().Core
273+
);
274+
});
275+
276+
it('no event emitted when fee address is 0', async () => {
277+
await systems()
278+
.Core.connect(owner)
279+
.setConfig(
280+
ethers.utils.formatBytes32String('burnUsd_feeAddress'),
281+
ethers.utils.hexZeroPad(ethers.constants.AddressZero, 32)
282+
);
283+
await assertEvent(tx, `IssuanceFeePaid`, systems().Core, true);
284+
});
285+
});
286+
});
287+
});

0 commit comments

Comments
 (0)