-
Notifications
You must be signed in to change notification settings - Fork 25
/
test.fork.ts
190 lines (155 loc) · 7.77 KB
/
test.fork.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
import hre, { ethers } from 'hardhat';
import { expect } from 'chai';
import { BigNumber, Contract } from 'ethers';
import { BigNumberish } from '@helpers/numbers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { WeightedPoolEncoder } from '@helpers/models/pools/weighted/encoder';
import { MAX_UINT256 } from '@helpers/constants';
import { defaultAbiCoder } from '@ethersproject/abi/lib/abi-coder';
import { describeForkTest, impersonate, getForkedNetwork, Task, TaskMode } from '@src';
import * as expectEvent from '@helpers/expectEvent';
import { sharedBeforeEach } from '@helpers/sharedBeforeEach';
describeForkTest.skip('BatchRelayerLibrary', 'mainnet', 15485000, function () {
let task: Task;
let relayer: Contract, library: Contract;
let sender: SignerWithAddress;
let vault: Contract, authorizer: Contract;
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const ETH_STETH_POOL = '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080';
const ETH_STETH_GAUGE = '0xcD4722B7c24C29e0413BDCd9e51404B4539D14aE';
const ETH_DAI_POOL = '0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a';
const ETH_DAI_GAUGE = '0x4ca6AC0509E6381Ca7CD872a6cdC0Fbf00600Fa1';
const STAKED_ETH_STETH_HOLDER = '0x4B581dedA2f2C0650C3dFC506C86a8C140d9f699';
// The holder also holds RETH_STABLE_GAUGE tokens
const RETH_STABLE_GAUGE = '0x79eF6103A513951a3b25743DB509E267685726B7';
const TRANSFER_EXTERNAL_USER_BALANCE_OP_KIND = 3;
const CHAINED_REFERENCE_PREFIX = 'ba10';
function toChainedReference(key: BigNumberish): BigNumber {
// The full padded prefix is 66 characters long, with 64 hex characters and the 0x prefix.
const paddedPrefix = `0x${CHAINED_REFERENCE_PREFIX}${'0'.repeat(64 - CHAINED_REFERENCE_PREFIX.length)}`;
return BigNumber.from(paddedPrefix).add(key);
}
before('run task', async () => {
task = new Task('20230314-batch-relayer-v5', TaskMode.TEST, getForkedNetwork(hre));
await task.run({ force: true });
library = await task.deployedInstance('BatchRelayerLibrary');
relayer = await task.instanceAt('BalancerRelayer', await library.getEntrypoint());
});
before('load vault and tokens', async () => {
const vaultTask = new Task('20210418-vault', TaskMode.READ_ONLY, getForkedNetwork(hre));
vault = await vaultTask.instanceAt('Vault', await library.getVault());
authorizer = await vaultTask.instanceAt('Authorizer', await vault.getAuthorizer());
});
before('load signers', async () => {
// We impersonate an account that holds staked BPT for the ETH_STETH Pool.
sender = await impersonate(STAKED_ETH_STETH_HOLDER);
});
before('approve relayer at the authorizer', async () => {
const relayerActionIds = await Promise.all(
['swap', 'batchSwap', 'joinPool', 'exitPool', 'setRelayerApproval', 'manageUserBalance'].map((action) =>
vault.getActionId(vault.interface.getSighash(action))
)
);
// We impersonate an account with the default admin role in order to be able to approve the relayer. This assumes
// such an account exists.
const admin = await impersonate(await authorizer.getRoleMember(await authorizer.DEFAULT_ADMIN_ROLE(), 0));
// Grant relayer permission to call all relayer functions
await authorizer.connect(admin).grantRoles(relayerActionIds, relayer.address);
});
sharedBeforeEach('approve relayer by the user', async () => {
await vault.connect(sender).setRelayerApproval(sender.address, relayer.address, true);
});
it('sender can unstake, exit, join and stake', async () => {
const destinationGauge = await task.instanceAt('IERC20', ETH_DAI_GAUGE);
expect(await destinationGauge.balanceOf(sender.address)).to.be.equal(0);
// We use the relayer as the intermediate token holder as that saves gas (since there's fewer transfers, relayer
// permission checks, etc.) and also sidesteps the issue that not all BPT has Vault allowance (which is required to
// transfer them via the Vault, e.g. for staking).
const stakedBalance = await (await task.instanceAt('IERC20', ETH_STETH_GAUGE)).balanceOf(sender.address);
// There's no chained output here as the input equals the output
const unstakeCalldata = library.interface.encodeFunctionData('gaugeWithdraw', [
ETH_STETH_GAUGE,
sender.address,
relayer.address,
stakedBalance,
]);
// Exit into WETH (it'd be more expensive to use ETH, and we'd have to use the relayer as an intermediary as we'd
// need to use said ETH).
const ethStethTokens: Array<string> = (await vault.getPoolTokens(ETH_STETH_POOL)).tokens;
const stableWethIndex = ethStethTokens.findIndex((token) => token.toLowerCase() == WETH.toLowerCase());
const exitCalldata = library.interface.encodeFunctionData('exitPool', [
ETH_STETH_POOL,
0, // Even if this a Stable Pool, the Batch Relayer is unaware of their encodings and the Weighted Pool encoding
// happens to match here
relayer.address,
relayer.address,
{
assets: ethStethTokens,
minAmountsOut: ethStethTokens.map(() => 0),
// Note that we use the same input as before
userData: defaultAbiCoder.encode(['uint256', 'uint256', 'uint256'], [0, stakedBalance, stableWethIndex]),
toInternalBalance: true,
},
// Only store a chained reference for the WETH amount out, as the rest will be zero
[{ key: toChainedReference(42), index: stableWethIndex }],
]);
// Join from WETH
const ethDaiTokens: Array<string> = (await vault.getPoolTokens(ETH_DAI_POOL)).tokens;
const ethDaiAmountsIn = ethDaiTokens.map((token) =>
token.toLowerCase() == WETH.toLowerCase() ? toChainedReference(42) : 0
);
const joinCalldata = library.interface.encodeFunctionData('joinPool', [
ETH_DAI_POOL,
0, // Weighted Pool
relayer.address,
relayer.address,
{
assets: ethDaiTokens,
maxAmountsIn: ethDaiTokens.map(() => MAX_UINT256),
userData: WeightedPoolEncoder.joinExactTokensInForBPTOut(ethDaiAmountsIn, 0),
fromInternalBalance: true, // Since we're joining from internal balance, we don't need to grant token allowance
},
0, // No eth
toChainedReference(17), // Store a reference for later staking
]);
const stakeCalldata = library.interface.encodeFunctionData('gaugeDeposit', [
ETH_DAI_GAUGE,
relayer.address,
sender.address,
toChainedReference(17), // Stake all BPT from the join
]);
await relayer.connect(sender).multicall([unstakeCalldata, exitCalldata, joinCalldata, stakeCalldata]);
expect(await destinationGauge.balanceOf(sender.address)).to.be.gt(0);
});
it('sender can update their gauge liquidity limits', async () => {
const tx = await relayer.connect(sender).multicall([
library.interface.encodeFunctionData('manageUserBalance', [
[RETH_STABLE_GAUGE, ETH_STETH_GAUGE].map((gauge) => ({
kind: TRANSFER_EXTERNAL_USER_BALANCE_OP_KIND,
asset: gauge,
amount: 1, // The amount must be non-zero to trigger the token trasnfer and liquidity limit update
sender: sender.address,
recipient: sender.address,
})),
0, // value: 0 (no need to transfer ETH)
]),
]);
const gaugeInterface = new ethers.utils.Interface([
'event UpdateLiquidityLimit(address indexed user, uint256 original_balance, uint256 original_supply, uint256 working_balance, uint256 working_supply)',
]);
expectEvent.inIndirectReceipt(
await tx.wait(),
gaugeInterface,
'UpdateLiquidityLimit',
{ user: sender.address },
RETH_STABLE_GAUGE
);
expectEvent.inIndirectReceipt(
await tx.wait(),
gaugeInterface,
'UpdateLiquidityLimit',
{ user: sender.address },
ETH_STETH_GAUGE
);
});
});