Skip to content

Commit 35d59ac

Browse files
authored
SCCP-313: USDC LP incentives for Base (#160)
* SCCP-313: USDC LP incentives for Base * Rename rewards distributor * Bump deployment version * Testnet e2e tests for SNX and USDC rewards * Typo in setting -> settings 🤦 * Fix for testnet tests * Mainnet test for USDC rewards * Fix USDC whale address on mainnet
1 parent 10ab9e0 commit 35d59ac

File tree

8 files changed

+793
-84
lines changed

8 files changed

+793
-84
lines changed

e2e/generateDeployments.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,20 @@ async function run() {
100100
perpsFactory.contracts.PerpsAccountProxy ?? perpsFactory.contracts.AccountProxy;
101101
}
102102

103-
const rd =
103+
const snxRewards =
104104
deployments?.state?.[`provision.spartan_council_pool_rewards`]?.artifacts?.imports
105105
?.spartan_council_pool_rewards;
106-
if (rd) {
107-
contracts[`RewardsDistributorForSpartanCouncilPool`] = rd.contracts.RewardsDistributor;
106+
if (snxRewards) {
107+
contracts[`RewardsDistributorForSpartanCouncilPoolSNX`] =
108+
snxRewards.contracts.RewardsDistributor;
109+
}
110+
111+
const usdcRewards =
112+
deployments?.state?.[`provision.sccp_313_spartan_council_pool_usdc_rewards`]?.artifacts?.imports
113+
?.sccp_313_spartan_council_pool_usdc_rewards;
114+
if (usdcRewards) {
115+
contracts[`RewardsDistributorForSpartanCouncilPoolUSDC`] =
116+
usdcRewards.contracts.RewardsDistributor;
108117
}
109118

110119
function mintableToken(provisionStep) {

e2e/tasks/getTokenRewardsDistributorRewardsAmount.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,25 @@ async function getTokenRewardsDistributorRewardsAmount({ distributorAddress }) {
1010
);
1111
const RewardsDistributor = new ethers.Contract(
1212
distributorAddress,
13-
['function rewardsAmount() view returns (uint256)'],
13+
[
14+
'function payoutToken() view returns (address)',
15+
'function rewardsAmount() view returns (uint256)',
16+
],
1417
provider
1518
);
16-
const rewardsAmount = await RewardsDistributor.rewardsAmount().catch(() => null);
17-
log({ rewardsAmount });
19+
const [payoutToken, rewardsAmount] = await Promise.all([
20+
RewardsDistributor.payoutToken().catch(() => null),
21+
RewardsDistributor.rewardsAmount().catch(() => null),
22+
]);
1823

19-
return parseFloat(ethers.utils.formatEther(rewardsAmount));
24+
const Token = new ethers.Contract(
25+
payoutToken,
26+
['function decimals() view returns (uint8)'],
27+
provider
28+
);
29+
const decimals = await Token.decimals().catch(() => null);
30+
31+
return parseFloat(ethers.utils.formatUnits(rewardsAmount, decimals));
2032
}
2133

2234
module.exports = {

e2e/tasks/setUSDCTokenBalance.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async function setUSDCTokenBalance({ wallet, balance }) {
3333
}
3434

3535
// Mainnet only!
36-
const friendlyWhale = '0x20FE51A9229EEf2cF8Ad9E89d91CAb9312cF3b7A';
36+
const friendlyWhale = '0xcdac0d6c6c59727a65f871236188350531885c43';
3737
const whaleBalance = parseFloat(
3838
ethers.utils.formatUnits(await Token.balanceOf(friendlyWhale), decimals)
3939
);
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
const crypto = require('crypto');
2+
const assert = require('assert');
3+
const { ethers } = require('ethers');
4+
require('../../inspect');
5+
const log = require('debug')(`e2e:${require('path').basename(__filename, '.e2e.js')}`);
6+
7+
const { getEthBalance } = require('../../tasks/getEthBalance');
8+
const { setEthBalance } = require('../../tasks/setEthBalance');
9+
const { setUSDCTokenBalance } = require('../../tasks/setUSDCTokenBalance');
10+
const { wrapCollateral } = require('../../tasks/wrapCollateral');
11+
const { getAccountOwner } = require('../../tasks/getAccountOwner');
12+
const { createAccount } = require('../../tasks/createAccount');
13+
const { getCollateralBalance } = require('../../tasks/getCollateralBalance');
14+
const { getAccountCollateral } = require('../../tasks/getAccountCollateral');
15+
const { isCollateralApproved } = require('../../tasks/isCollateralApproved');
16+
const { approveCollateral } = require('../../tasks/approveCollateral');
17+
const { depositCollateral } = require('../../tasks/depositCollateral');
18+
const { delegateCollateral } = require('../../tasks/delegateCollateral');
19+
const { doPriceUpdate } = require('../../tasks/doPriceUpdate');
20+
const { syncTime } = require('../../tasks/syncTime');
21+
const { getTokenBalance } = require('../../tasks/getTokenBalance');
22+
const { transferToken } = require('../../tasks/transferToken');
23+
const { distributeRewards } = require('../../tasks/distributeRewards');
24+
const { getPoolOwner } = require('../../tasks/getPoolOwner');
25+
const { getTokenRewardsDistributorInfo } = require('../../tasks/getTokenRewardsDistributorInfo');
26+
const {
27+
getTokenRewardsDistributorRewardsAmount,
28+
} = require('../../tasks/getTokenRewardsDistributorRewardsAmount');
29+
const { getAvailableRewards } = require('../../tasks/getAvailableRewards');
30+
const { claimRewards } = require('../../tasks/claimRewards');
31+
32+
const {
33+
contracts: {
34+
RewardsDistributorForSpartanCouncilPoolUSDC: distributorAddress,
35+
USDCToken: payoutToken,
36+
37+
CoreProxy: rewardManager,
38+
SynthUSDCToken: collateralType,
39+
},
40+
} = require('../../deployments/meta.json');
41+
42+
describe(require('path').basename(__filename, '.e2e.js'), function () {
43+
const accountId = parseInt(`1337${crypto.randomInt(1000)}`);
44+
const provider = new ethers.providers.JsonRpcProvider(
45+
process.env.RPC_URL || 'http://127.0.0.1:8545'
46+
);
47+
const wallet = ethers.Wallet.createRandom().connect(provider);
48+
const address = wallet.address;
49+
const privateKey = wallet.privateKey;
50+
51+
let snapshot;
52+
let initialBalance;
53+
let initialRewardsAmount;
54+
55+
before('Create snapshot', async () => {
56+
snapshot = await provider.send('evm_snapshot', []);
57+
log('Create snapshot', { snapshot });
58+
59+
initialBalance = await getTokenBalance({
60+
walletAddress: distributorAddress,
61+
tokenAddress: payoutToken,
62+
});
63+
log('Initial balance', { initialBalance });
64+
65+
initialRewardsAmount = await getTokenRewardsDistributorRewardsAmount({ distributorAddress });
66+
log('Initial rewards amount', { initialRewardsAmount });
67+
});
68+
69+
after('Restore snapshot', async () => {
70+
log('Restore snapshot', { snapshot });
71+
await provider.send('evm_revert', [snapshot]);
72+
});
73+
74+
it('should sync time of the fork', async () => {
75+
await syncTime();
76+
});
77+
78+
it('should validate Rewards Distributor info', async () => {
79+
const info = await getTokenRewardsDistributorInfo({ distributorAddress });
80+
assert.equal(info.name, 'Spartan Council Pool USDC Rewards', 'name');
81+
assert.equal(info.poolId, 1, 'poolId');
82+
assert.equal(info.collateralType, collateralType, 'collateralType');
83+
assert.equal(
84+
`${info.payoutToken}`.toLowerCase(),
85+
`${payoutToken}`.toLowerCase(),
86+
'payoutToken'
87+
);
88+
assert.equal(info.precision, 10 ** 6, 'precision');
89+
assert.equal(`${info.token}`.toLowerCase(), `${payoutToken}`.toLowerCase(), 'token');
90+
assert.equal(info.rewardManager, rewardManager, 'rewardManager');
91+
assert.equal(info.shouldFailPayout, false, 'shouldFailPayout');
92+
});
93+
94+
it('should create new random wallet', async () => {
95+
log({ wallet: wallet.address, pk: wallet.privateKey });
96+
assert.ok(wallet.address);
97+
});
98+
99+
it('should set ETH balance to 100', async () => {
100+
assert.equal(await getEthBalance({ address }), 0, 'New wallet has 0 ETH balance');
101+
await setEthBalance({ address, balance: 100 });
102+
assert.equal(await getEthBalance({ address }), 100);
103+
});
104+
105+
it('should create user account', async () => {
106+
assert.equal(
107+
await getAccountOwner({ accountId }),
108+
ethers.constants.AddressZero,
109+
'New wallet should not have an account yet'
110+
);
111+
await createAccount({ wallet, accountId });
112+
assert.equal(await getAccountOwner({ accountId }), address);
113+
});
114+
115+
it(`should set USDC balance to 1_000`, async () => {
116+
assert.equal(
117+
await getCollateralBalance({ address, symbol: 'USDC' }),
118+
0,
119+
'New wallet has 0 USDC balance'
120+
);
121+
await setUSDCTokenBalance({
122+
wallet,
123+
balance: 1_000,
124+
});
125+
assert.equal(await getCollateralBalance({ address, symbol: 'USDC' }), 1_000);
126+
});
127+
128+
it('should approve USDC spending for SpotMarket', async () => {
129+
assert.equal(
130+
await isCollateralApproved({
131+
address,
132+
symbol: 'USDC',
133+
spenderAddress: require('../../deployments/SpotMarketProxy.json').address,
134+
}),
135+
false,
136+
'New wallet has not allowed SpotMarket USDC spending'
137+
);
138+
await approveCollateral({
139+
privateKey,
140+
symbol: 'USDC',
141+
spenderAddress: require('../../deployments/SpotMarketProxy.json').address,
142+
});
143+
assert.equal(
144+
await isCollateralApproved({
145+
address,
146+
symbol: 'USDC',
147+
spenderAddress: require('../../deployments/SpotMarketProxy.json').address,
148+
}),
149+
true
150+
);
151+
});
152+
153+
it(`should wrap 1_000 USDC`, async () => {
154+
const balance = await wrapCollateral({ wallet, symbol: 'USDC', amount: 1_000 });
155+
assert.equal(balance, 1_000);
156+
});
157+
158+
it('should approve sUSDC spending for CoreProxy', async () => {
159+
assert.equal(
160+
await isCollateralApproved({
161+
address,
162+
symbol: 'sUSDC',
163+
spenderAddress: require('../../deployments/CoreProxy.json').address,
164+
}),
165+
false,
166+
'New wallet has not allowed CoreProxy sUSDC spending'
167+
);
168+
await approveCollateral({
169+
privateKey,
170+
symbol: 'sUSDC',
171+
spenderAddress: require('../../deployments/CoreProxy.json').address,
172+
});
173+
assert.equal(
174+
await isCollateralApproved({
175+
address,
176+
symbol: 'sUSDC',
177+
spenderAddress: require('../../deployments/CoreProxy.json').address,
178+
}),
179+
true
180+
);
181+
});
182+
183+
it(`should deposit 1_000 sUSDC into the system`, async () => {
184+
assert.equal(await getCollateralBalance({ address, symbol: 'sUSDC' }), 1_000);
185+
assert.deepEqual(await getAccountCollateral({ accountId, symbol: 'sUSDC' }), {
186+
totalDeposited: 0,
187+
totalAssigned: 0,
188+
totalLocked: 0,
189+
});
190+
191+
await depositCollateral({
192+
privateKey,
193+
symbol: 'sUSDC',
194+
accountId,
195+
amount: 1_000,
196+
});
197+
198+
assert.equal(await getCollateralBalance({ address, symbol: 'sUSDC' }), 0);
199+
assert.deepEqual(await getAccountCollateral({ accountId, symbol: 'sUSDC' }), {
200+
totalDeposited: 1_000,
201+
totalAssigned: 0,
202+
totalLocked: 0,
203+
});
204+
});
205+
206+
it('should make a price update', async () => {
207+
// We must sync timestamp of the fork before making price updates
208+
await syncTime();
209+
210+
// delegating collateral and views requiring price will fail if there's no price update within the last hour,
211+
// so we send off a price update just to be safe
212+
await doPriceUpdate({
213+
wallet,
214+
marketId: 100,
215+
settlementStrategyId: require('../../deployments/extras.json').eth_pyth_settlement_strategy,
216+
});
217+
await doPriceUpdate({
218+
wallet,
219+
marketId: 200,
220+
settlementStrategyId: require('../../deployments/extras.json').btc_pyth_settlement_strategy,
221+
});
222+
});
223+
224+
it(`should delegate 1_000 sUSDC into the Spartan Council pool`, async () => {
225+
assert.deepEqual(await getAccountCollateral({ accountId, symbol: 'sUSDC' }), {
226+
totalDeposited: 1_000,
227+
totalAssigned: 0,
228+
totalLocked: 0,
229+
});
230+
await delegateCollateral({
231+
privateKey,
232+
symbol: 'sUSDC',
233+
accountId,
234+
amount: 1_000,
235+
poolId: 1,
236+
});
237+
assert.deepEqual(await getAccountCollateral({ accountId, symbol: 'sUSDC' }), {
238+
totalDeposited: 1_000,
239+
totalAssigned: 1_000,
240+
totalLocked: 0,
241+
});
242+
});
243+
244+
it('should fund RewardDistributor with 1_000 USDC', async () => {
245+
await setUSDCTokenBalance({ wallet, balance: 1_000 });
246+
247+
await transferToken({
248+
privateKey,
249+
tokenAddress: payoutToken,
250+
targetWalletAddress: distributorAddress,
251+
amount: 1_000,
252+
});
253+
254+
assert.equal(
255+
Math.floor(
256+
await getTokenBalance({ walletAddress: distributorAddress, tokenAddress: payoutToken })
257+
),
258+
Math.floor(initialBalance + 1_000),
259+
'Rewards Distributor has 1_000 extra USDC on its balance'
260+
);
261+
});
262+
263+
it('should distribute 1_000 USDC rewards', async () => {
264+
const poolId = 1;
265+
const poolOwner = await getPoolOwner({ poolId });
266+
log({ poolOwner });
267+
268+
await provider.send('anvil_impersonateAccount', [poolOwner]);
269+
const signer = provider.getSigner(poolOwner);
270+
271+
const amount = ethers.utils.parseUnits(`${1_000}`, 6); // the number must be in 6 decimals
272+
const start = Math.floor(Date.now() / 1_000);
273+
const duration = 10;
274+
275+
await distributeRewards({
276+
wallet: signer,
277+
distributorAddress,
278+
poolId,
279+
collateralType,
280+
amount,
281+
start,
282+
duration,
283+
});
284+
285+
await provider.send('anvil_stopImpersonatingAccount', [poolOwner]);
286+
287+
assert.equal(
288+
await getTokenRewardsDistributorRewardsAmount({ distributorAddress }),
289+
initialRewardsAmount + 1_000,
290+
'should have 1_000 extra tokens in rewards'
291+
);
292+
});
293+
294+
it('should claim USDC rewards', async () => {
295+
const poolId = 1;
296+
297+
const availableRewards = await getAvailableRewards({
298+
accountId,
299+
poolId,
300+
collateralType,
301+
distributorAddress,
302+
});
303+
304+
assert.ok(availableRewards > 0, 'should have some rewards to claim');
305+
306+
assert.equal(
307+
await getTokenBalance({ walletAddress: address, tokenAddress: payoutToken }),
308+
0,
309+
'Wallet has 0 USDC balance BEFORE claim'
310+
);
311+
312+
await claimRewards({
313+
wallet,
314+
accountId,
315+
poolId,
316+
collateralType,
317+
distributorAddress,
318+
});
319+
320+
const postClaimBalance = await getTokenBalance({
321+
walletAddress: address,
322+
tokenAddress: payoutToken,
323+
});
324+
assert.ok(postClaimBalance > 0, 'Wallet has some non-zero USDC balance AFTER claim');
325+
326+
assert.equal(
327+
Math.floor(await getTokenRewardsDistributorRewardsAmount({ distributorAddress })),
328+
Math.floor(initialRewardsAmount + 1_000 - postClaimBalance),
329+
'should deduct claimed token amount from total distributor rewards amount'
330+
);
331+
});
332+
});

0 commit comments

Comments
 (0)