Skip to content

Commit ecd3ccf

Browse files
Add integration tests for settle and claim (Synthetixio#1310)
* Support settlements in exchanging behavior * Testing claims in integration tests * Address PR feedback, increase timeouts, improve test for forking * Bigger tolerance for debt comparison * Approve bridge tokens during bootstrap
1 parent 3a1273a commit ecd3ccf

13 files changed

+273
-45
lines changed

hardhat.config.js

+2
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ module.exports = {
5050
allowUnlimitedContractSize: true,
5151
gasPrice: GAS_PRICE,
5252
initialDate: new Date(inflationStartTimestampInSecs * 1000).toISOString(),
53+
timeout: 600000,
5354
// Note: forking settings are injected at runtime by hardhat/tasks/task-node.js
5455
},
5556
localhost: {
5657
gas: 12e6,
5758
blockGasLimit: 12e6,
5859
url: 'http://localhost:8545',
60+
timeout: 600000,
5961
},
6062
},
6163
gasReporter: {

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/integration/behaviors/exchange.behavior.js

+22-22
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ const ethers = require('ethers');
22
const { assert } = require('../../contracts/common');
33
const { toBytes32 } = require('../../../index');
44
const { ensureBalance } = require('../utils/balances');
5-
const { ignoreMinimumStakeTime } = require('../utils/stakeTime');
5+
const { ignoreWaitingPeriod } = require('../utils/exchanging');
66

77
function itCanExchange({ ctx }) {
8-
describe('exchanging', () => {
8+
describe('exchanging and settling', () => {
99
const sUSDAmount = ethers.utils.parseEther('100');
1010

1111
let owner;
12-
let balancesETH, balancesUSD;
13-
let Synthetix, Exchanger, SynthsETH, SynthsUSD;
12+
let balancesETH, originialPendingSettlements;
13+
let Synthetix, Exchanger, SynthsETH;
1414

1515
before('target contracts and users', () => {
16-
({ Synthetix, Exchanger, SynthsETH, SynthsUSD } = ctx.contracts);
16+
({ Synthetix, Exchanger, SynthsETH } = ctx.contracts);
1717

1818
owner = ctx.users.owner;
1919
});
@@ -27,6 +27,12 @@ function itCanExchange({ ctx }) {
2727
balancesETH = await SynthsETH.balanceOf(owner.address);
2828
});
2929

30+
before('record pending settlements', async () => {
31+
const { numEntries } = await Exchanger.settlementOwing(owner.address, toBytes32('sETH'));
32+
33+
originialPendingSettlements = numEntries;
34+
});
35+
3036
before('perform the exchange', async () => {
3137
Synthetix = Synthetix.connect(owner);
3238

@@ -44,30 +50,24 @@ function itCanExchange({ ctx }) {
4450
assert.bnEqual(await SynthsETH.balanceOf(owner.address), balancesETH.add(expectedAmount));
4551
});
4652

47-
// TODO: Disabled until we understand time granularity in the ops tool L2 chain
48-
describe.skip('when the owner exchanges sETH to sUSD', () => {
49-
ignoreMinimumStakeTime({ ctx });
53+
it('shows that the user now has pending settlements', async () => {
54+
const { numEntries } = await Exchanger.settlementOwing(owner.address, toBytes32('sETH'));
5055

51-
before('record balances', async () => {
52-
balancesUSD = await SynthsUSD.balanceOf(owner.address);
53-
balancesETH = await SynthsETH.balanceOf(owner.address);
54-
});
56+
assert.bnEqual(numEntries, originialPendingSettlements.add(ethers.constants.One));
57+
});
5558

56-
before('perform the exchange', async () => {
57-
Synthetix = Synthetix.connect(owner);
59+
describe('when settle is called', () => {
60+
ignoreWaitingPeriod({ ctx });
5861

59-
const tx = await Synthetix.exchange(toBytes32('sETH'), balancesETH, toBytes32('sUSD'));
62+
before('settle', async () => {
63+
const tx = await Synthetix.settle(toBytes32('sETH'));
6064
await tx.wait();
6165
});
6266

63-
it('receives the expected amount of sUSD', async () => {
64-
const [expectedAmount, ,] = await Exchanger.getAmountsForExchange(
65-
balancesETH,
66-
toBytes32('sETH'),
67-
toBytes32('sUSD')
68-
);
67+
it('shows that the user no longer has pending settlements', async () => {
68+
const { numEntries } = await Exchanger.settlementOwing(owner.address, toBytes32('sETH'));
6969

70-
assert.bnEqual(await SynthsUSD.balanceOf(owner.address), balancesUSD.add(expectedAmount));
70+
assert.bnEqual(numEntries, ethers.constants.Zero);
7171
});
7272
});
7373
});
+71-19
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
const ethers = require('ethers');
2+
const { toBytes32 } = require('../../../index');
23
const { assert } = require('../../contracts/common');
4+
const { exchangeSomething, ignoreFeePeriodDuration } = require('../utils/exchanging');
35
const { ensureBalance } = require('../utils/balances');
4-
const { ignoreMinimumStakeTime } = require('../utils/stakeTime');
6+
const { ignoreMinimumStakeTime } = require('../utils/staking');
7+
const { skipIfL2 } = require('../utils/l2');
58

6-
function itCanMintAndBurn({ ctx }) {
7-
describe('staking', () => {
9+
function itCanStake({ ctx }) {
10+
describe('staking and claiming', () => {
811
const SNXAmount = ethers.utils.parseEther('100');
9-
const sUSDamount = ethers.utils.parseEther('1');
12+
const amountToIssueAndBurnsUSD = ethers.utils.parseEther('1');
1013

1114
let user;
12-
let Synthetix, SynthsUSD;
13-
let balancesUSD;
15+
let Synthetix, SynthsUSD, FeePool;
16+
let balancesUSD, debtsUSD;
1417

1518
before('target contracts and users', () => {
16-
({ Synthetix, SynthsUSD } = ctx.contracts);
19+
({ Synthetix, SynthsUSD, FeePool } = ctx.contracts);
1720

1821
user = ctx.users.someUser;
1922
});
@@ -30,41 +33,90 @@ function itCanMintAndBurn({ ctx }) {
3033
before('issue sUSD', async () => {
3134
Synthetix = Synthetix.connect(user);
3235

33-
const tx = await Synthetix.issueSynths(sUSDamount);
36+
const tx = await Synthetix.issueSynths(amountToIssueAndBurnsUSD);
3437
await tx.wait();
3538
});
3639

3740
it('issues the expected amount of sUSD', async () => {
38-
assert.bnEqual(await SynthsUSD.balanceOf(user.address), balancesUSD.add(sUSDamount));
41+
assert.bnEqual(
42+
await SynthsUSD.balanceOf(user.address),
43+
balancesUSD.add(amountToIssueAndBurnsUSD)
44+
);
45+
});
46+
47+
describe('claiming', () => {
48+
// TODO: Disabled until Optimism supports 5s time granularity.
49+
// We can set fee period duration to 5s, but we dont want this test
50+
// to wait 3m, which is the current time granularity.
51+
skipIfL2({
52+
ctx,
53+
reason:
54+
'ops L2 time granularity needs to be less than 3m, so we cant close the fee period',
55+
});
56+
57+
before('exchange something', async () => {
58+
await exchangeSomething({ ctx });
59+
});
60+
61+
describe('when the fee period closes', () => {
62+
ignoreFeePeriodDuration({ ctx });
63+
64+
before('close the current fee period', async () => {
65+
FeePool = FeePool.connect(ctx.users.owner);
66+
67+
const tx = await FeePool.closeCurrentFeePeriod();
68+
await tx.wait();
69+
});
70+
71+
describe('when the user claims rewards', () => {
72+
before('record balances', async () => {
73+
balancesUSD = await SynthsUSD.balanceOf(user.address);
74+
});
75+
76+
before('claim', async () => {
77+
FeePool = FeePool.connect(user);
78+
79+
const tx = await FeePool.claimFees();
80+
await tx.wait();
81+
});
82+
83+
it('shows a slight increase in the users sUSD balance', async () => {
84+
assert.bnGt(await SynthsUSD.balanceOf(user.address), balancesUSD);
85+
});
86+
});
87+
});
3988
});
4089
});
4190

4291
describe('when the user burns sUSD', () => {
4392
ignoreMinimumStakeTime({ ctx });
4493

45-
before('record values', async () => {
46-
balancesUSD = await SynthsUSD.balanceOf(user.address);
94+
before('record debt', async () => {
95+
debtsUSD = await Synthetix.debtBalanceOf(user.address, toBytes32('sUSD'));
4796
});
4897

4998
before('burn sUSD', async () => {
5099
Synthetix = Synthetix.connect(user);
51100

52-
const tx = await Synthetix.burnSynths(sUSDamount);
101+
const tx = await Synthetix.burnSynths(amountToIssueAndBurnsUSD);
53102
await tx.wait();
54103
});
55104

56-
it('burnt the expected amount of sUSD', async () => {
57-
const newBalancesUSD = await SynthsUSD.balanceOf(user.address);
58-
const expected = balancesUSD.sub(sUSDamount);
59-
const delta = newBalancesUSD.sub(expected);
60-
const variance = ethers.utils.parseUnits('2', 'gwei');
105+
it('reduced the expected amount of debt', async () => {
106+
const newDebtsUSD = await Synthetix.debtBalanceOf(user.address, toBytes32('sUSD'));
107+
const debtReduction = debtsUSD.sub(newDebtsUSD);
61108

62-
assert.bnLt(delta, variance);
109+
const tolerance = ethers.utils.parseUnits('40', 'gwei');
110+
assert.bnClose(
111+
debtReduction.toString(),
112+
amountToIssueAndBurnsUSD.toString(),
113+
tolerance.toString()
114+
);
63115
});
64116
});
65117
});
66118
}
67119

68120
module.exports = {
69-
itCanMintAndBurn,
121+
itCanStake,
70122
};
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
const { bootstrapL1 } = require('../utils/bootstrap');
22
const { itCanExchange } = require('../behaviors/exchange.behavior');
3-
const { itCanMintAndBurn } = require('../behaviors/stake.behavior');
3+
const { itCanStake } = require('../behaviors/stake.behavior');
44
const { itBehavesLikeAnERC20 } = require('../behaviors/erc20.behavior');
55

66
describe('Synthetix integration tests (L1)', () => {
77
const ctx = this;
88
bootstrapL1({ ctx });
99

1010
itCanExchange({ ctx });
11-
itCanMintAndBurn({ ctx });
11+
itCanStake({ ctx });
1212
itBehavesLikeAnERC20({ ctx });
1313
});
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
const { bootstrapL2 } = require('../utils/bootstrap');
22
const { itCanExchange } = require('../behaviors/exchange.behavior');
3-
const { itCanMintAndBurn } = require('../behaviors/stake.behavior');
3+
const { itCanStake } = require('../behaviors/stake.behavior');
44
const { itBehavesLikeAnERC20 } = require('../behaviors/erc20.behavior');
55

66
describe('Synthetix integration tests (L2)', () => {
77
const ctx = this;
88
bootstrapL2({ ctx });
99

1010
itCanExchange({ ctx });
11-
itCanMintAndBurn({ ctx });
11+
itCanStake({ ctx });
1212
itBehavesLikeAnERC20({ ctx });
1313
});

test/integration/utils/bootstrap.js

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { Watcher } = require('@eth-optimism/watcher');
66
const { connectContracts } = require('./contracts');
77
const { updateExchangeRatesIfNeeded } = require('./rates');
88
const { ensureBalance } = require('./balances');
9+
const { approveBridge } = require('./bridge');
910

1011
function bootstrapL1({ ctx }) {
1112
before('bootstrap layer 1 instance', async () => {
@@ -82,6 +83,8 @@ function bootstrapDual({ ctx }) {
8283
await updateExchangeRatesIfNeeded({ ctx: ctx.l1 });
8384
await updateExchangeRatesIfNeeded({ ctx: ctx.l2 });
8485

86+
await approveBridge({ ctx: ctx.l1, amount: ethers.utils.parseEther('100000000') });
87+
8588
await ensureBalance({
8689
ctx: ctx.l2,
8790
symbol: 'SNX',

test/integration/utils/bridge.js

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const ethers = require('ethers');
12
const { finalizationOnL2 } = require('./watchers');
23

34
async function deposit({ ctx, from, to, amount }) {
@@ -23,7 +24,30 @@ async function withdraw() {
2324
// TODO
2425
}
2526

27+
async function approveBridge({ ctx, amount }) {
28+
const { Synthetix, SynthetixBridgeToOptimism } = ctx.contracts;
29+
let { SynthetixBridgeEscrow } = ctx.contracts;
30+
SynthetixBridgeEscrow = SynthetixBridgeEscrow.connect(ctx.users.owner);
31+
32+
let tx;
33+
34+
tx = await SynthetixBridgeEscrow.approveBridge(
35+
Synthetix.address,
36+
SynthetixBridgeToOptimism.address,
37+
ethers.constants.Zero
38+
);
39+
await tx.wait();
40+
41+
tx = await SynthetixBridgeEscrow.approveBridge(
42+
Synthetix.address,
43+
SynthetixBridgeToOptimism.address,
44+
amount
45+
);
46+
await tx.wait();
47+
}
48+
2649
module.exports = {
2750
deposit,
2851
withdraw,
52+
approveBridge,
2953
};

test/integration/utils/exchanging.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const ethers = require('ethers');
2+
const { ensureBalance } = require('./balances');
3+
const { forceSetSystemSetting } = require('./settings');
4+
const { toBytes32 } = require('../../../index');
5+
const { wait } = require('./wait');
6+
7+
function ignoreWaitingPeriod({ ctx }) {
8+
before('record and reduce waitingPeriodSecs', async () => {
9+
let { SystemSettings } = ctx.contracts;
10+
SystemSettings = SystemSettings.connect(ctx.users.owner);
11+
12+
ctx.waitingPeriodSecs = await SystemSettings.waitingPeriodSecs();
13+
14+
const tx = await SystemSettings.setWaitingPeriodSecs(0);
15+
await tx.wait();
16+
});
17+
18+
after('restore waiting period', async () => {
19+
let { SystemSettings } = ctx.contracts;
20+
SystemSettings = SystemSettings.connect(ctx.users.owner);
21+
22+
const tx = await SystemSettings.setWaitingPeriodSecs(ctx.waitingPeriodSecs);
23+
await tx.wait();
24+
});
25+
}
26+
27+
async function exchangeSomething({ ctx }) {
28+
let { Synthetix } = ctx.contracts;
29+
Synthetix = Synthetix.connect(ctx.users.owner);
30+
31+
const sUSDAmount = ethers.utils.parseEther('10');
32+
await ensureBalance({ ctx, symbol: 'sUSD', user: ctx.users.owner, balance: sUSDAmount });
33+
34+
const tx = await Synthetix.exchange(toBytes32('sUSD'), sUSDAmount, toBytes32('sETH'));
35+
await tx.wait();
36+
}
37+
38+
function ignoreFeePeriodDuration({ ctx }) {
39+
before('record and reduce feePeriodDuration', async () => {
40+
const { SystemSettings } = ctx.contracts;
41+
42+
ctx.feePeriodDuration = await SystemSettings.feePeriodDuration();
43+
44+
// SystemSettings.setFeePeriodDuration() enforces a minimum value of 1 day,
45+
// which is not ideal for tests.
46+
// Instead, we force it by writing to flexible storage directly.
47+
await forceSetSystemSetting({ ctx, settingName: 'feePeriodDuration', newValue: 5 });
48+
49+
await wait({ seconds: 5 });
50+
});
51+
52+
after('restore feePeriodDuration', async () => {
53+
let { SystemSettings } = ctx.contracts;
54+
SystemSettings = SystemSettings.connect(ctx.users.owner);
55+
56+
const tx = await SystemSettings.setFeePeriodDuration(ctx.feePeriodDuration);
57+
await tx.wait();
58+
});
59+
}
60+
61+
module.exports = {
62+
ignoreWaitingPeriod,
63+
exchangeSomething,
64+
ignoreFeePeriodDuration,
65+
};

0 commit comments

Comments
 (0)