-
Notifications
You must be signed in to change notification settings - Fork 327
/
Copy pathfees_test.ts
328 lines (286 loc) · 12.6 KB
/
fees_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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import {
type AccountWallet,
type AztecAddress,
type AztecNode,
type Logger,
type PXE,
SignerlessWallet,
createLogger,
sleep,
} from '@aztec/aztec.js';
import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint';
import { EthAddress, FEE_FUNDING_FOR_TESTER_ACCOUNT, GasSettings, computePartialAddress } from '@aztec/circuits.js';
import { createL1Clients } from '@aztec/ethereum';
import { TestERC20Abi } from '@aztec/l1-artifacts';
import { AppSubscriptionContract } from '@aztec/noir-contracts.js/AppSubscription';
import { CounterContract } from '@aztec/noir-contracts.js/Counter';
import { FPCContract } from '@aztec/noir-contracts.js/FPC';
import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice';
import { TokenContract as BananaCoin } from '@aztec/noir-contracts.js/Token';
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
import { getContract } from 'viem';
import { MNEMONIC } from '../fixtures/fixtures.js';
import { type ISnapshotManager, addAccounts, createSnapshotManager } from '../fixtures/snapshot_manager.js';
import { mintTokensToPrivate } from '../fixtures/token_utils.js';
import {
type BalancesFn,
ensureAccountsPubliclyDeployed,
getBalancesFn,
setupCanonicalFeeJuice,
} from '../fixtures/utils.js';
import { FeeJuicePortalTestingHarnessFactory, type GasBridgingTestHarness } from '../shared/gas_portal_test_harness.js';
const { E2E_DATA_PATH: dataPath } = process.env;
/**
* Test fixture for testing fees. Provides the following snapshots:
* InitialAccounts: Initializes 3 Schnorr account contracts.
* PublicDeployAccounts: Deploys the accounts publicly.
* DeployFeeJuice: Deploys the Fee Juice contract.
* FPCSetup: Deploys BananaCoin and FPC contracts, and bridges gas from L1.
* FundAlice: Mints private and public bananas to Alice.
* SetupSubscription: Deploys a counter contract and a subscription contract, and mints Fee Juice to the subscription contract.
*/
export class FeesTest {
private snapshotManager: ISnapshotManager;
private wallets: AccountWallet[] = [];
public logger: Logger;
public pxe!: PXE;
public aztecNode!: AztecNode;
public aliceWallet!: AccountWallet;
public aliceAddress!: AztecAddress;
public bobWallet!: AccountWallet;
public bobAddress!: AztecAddress;
public sequencerAddress!: AztecAddress;
public coinbase!: EthAddress;
public fpcAdmin!: AztecAddress;
public gasSettings!: GasSettings;
public feeJuiceContract!: FeeJuiceContract;
public bananaCoin!: BananaCoin;
public bananaFPC!: FPCContract;
public counterContract!: CounterContract;
public subscriptionContract!: AppSubscriptionContract;
public feeJuiceBridgeTestHarness!: GasBridgingTestHarness;
public getCoinbaseBalance!: () => Promise<bigint>;
public getGasBalanceFn!: BalancesFn;
public getBananaPublicBalanceFn!: BalancesFn;
public getBananaPrivateBalanceFn!: BalancesFn;
public readonly ALICE_INITIAL_BANANAS = BigInt(1e22);
public readonly SUBSCRIPTION_AMOUNT = BigInt(1e19);
public readonly APP_SPONSORED_TX_GAS_LIMIT = BigInt(10e9);
constructor(testName: string) {
this.logger = createLogger(`e2e:e2e_fees:${testName}`);
this.snapshotManager = createSnapshotManager(`e2e_fees/${testName}`, dataPath);
}
async setup() {
const context = await this.snapshotManager.setup();
await context.aztecNode.setConfig({ feeRecipient: this.sequencerAddress, coinbase: this.coinbase });
return this;
}
async teardown() {
await this.snapshotManager.teardown();
}
async catchUpProvenChain() {
const bn = await this.aztecNode.getBlockNumber();
while ((await this.aztecNode.getProvenBlockNumber()) < bn) {
await sleep(1000);
}
}
async mintAndBridgeFeeJuice(address: AztecAddress, amount: bigint) {
const claim = await this.feeJuiceBridgeTestHarness.prepareTokensOnL1(amount, address);
const { claimSecret: secret, messageLeafIndex: index } = claim;
await this.feeJuiceContract.methods.claim(address, amount, secret, index).send().wait();
}
/** Alice mints bananaCoin tokens privately to the target address and redeems them. */
async mintPrivateBananas(amount: bigint, address: AztecAddress) {
const balanceBefore = await this.bananaCoin.methods.balance_of_private(address).simulate();
await mintTokensToPrivate(this.bananaCoin, this.aliceWallet, address, amount);
const balanceAfter = await this.bananaCoin.methods.balance_of_private(address).simulate();
expect(balanceAfter).toEqual(balanceBefore + amount);
}
public async applyBaseSnapshots() {
await this.applyInitialAccountsSnapshot();
await this.applyPublicDeployAccountsSnapshot();
await this.applySetupFeeJuiceSnapshot();
await this.applyDeployBananaTokenSnapshot();
}
async applyInitialAccountsSnapshot() {
await this.snapshotManager.snapshot(
'initial_accounts',
addAccounts(3, this.logger),
async ({ accountKeys }, { pxe, aztecNode, aztecNodeConfig }) => {
this.pxe = pxe;
this.aztecNode = aztecNode;
this.gasSettings = GasSettings.default({ maxFeesPerGas: (await this.aztecNode.getCurrentBaseFees()).mul(2) });
const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], 1));
await Promise.all(accountManagers.map(a => a.register()));
this.wallets = await Promise.all(accountManagers.map(a => a.getWallet()));
this.wallets.forEach((w, i) => this.logger.verbose(`Wallet ${i} address: ${w.getAddress()}`));
[this.aliceWallet, this.bobWallet] = this.wallets.slice(0, 2);
[this.aliceAddress, this.bobAddress, this.sequencerAddress] = this.wallets.map(w => w.getAddress());
// We set Alice as the FPC admin to avoid the need for deployment of another account.
this.fpcAdmin = this.aliceAddress;
this.feeJuiceContract = await FeeJuiceContract.at(getCanonicalFeeJuice().address, this.aliceWallet);
const bobInstance = await this.bobWallet.getContractInstance(this.bobAddress);
if (!bobInstance) {
throw new Error('Bob instance not found');
}
await this.aliceWallet.registerAccount(accountKeys[1][0], computePartialAddress(bobInstance));
this.coinbase = EthAddress.random();
const { publicClient, walletClient } = createL1Clients(aztecNodeConfig.l1RpcUrl, MNEMONIC);
this.feeJuiceBridgeTestHarness = await FeeJuicePortalTestingHarnessFactory.create({
aztecNode: aztecNode,
pxeService: pxe,
publicClient: publicClient,
walletClient: walletClient,
wallet: this.aliceWallet,
logger: this.logger,
});
},
);
}
async applyPublicDeployAccountsSnapshot() {
await this.snapshotManager.snapshot('public_deploy_accounts', () =>
ensureAccountsPubliclyDeployed(this.aliceWallet, this.wallets),
);
}
async applySetupFeeJuiceSnapshot() {
await this.snapshotManager.snapshot(
'setup_fee_juice',
async context => {
await setupCanonicalFeeJuice(
new SignerlessWallet(
context.pxe,
new DefaultMultiCallEntrypoint(context.aztecNodeConfig.l1ChainId, context.aztecNodeConfig.version),
),
);
},
async (_data, context) => {
this.feeJuiceContract = await FeeJuiceContract.at(ProtocolContractAddress.FeeJuice, this.aliceWallet);
this.getGasBalanceFn = getBalancesFn('⛽', this.feeJuiceContract.methods.balance_of_public, this.logger);
const { publicClient, walletClient } = createL1Clients(context.aztecNodeConfig.l1RpcUrl, MNEMONIC);
this.feeJuiceBridgeTestHarness = await FeeJuicePortalTestingHarnessFactory.create({
aztecNode: context.aztecNode,
pxeService: context.pxe,
publicClient: publicClient,
walletClient: walletClient,
wallet: this.aliceWallet,
logger: this.logger,
});
},
);
}
async applyDeployBananaTokenSnapshot() {
await this.snapshotManager.snapshot(
'deploy_banana_token',
async () => {
const bananaCoin = await BananaCoin.deploy(this.aliceWallet, this.aliceAddress, 'BC', 'BC', 18n)
.send()
.deployed();
this.logger.info(`BananaCoin deployed at ${bananaCoin.address}`);
return { bananaCoinAddress: bananaCoin.address };
},
async ({ bananaCoinAddress }) => {
this.bananaCoin = await BananaCoin.at(bananaCoinAddress, this.aliceWallet);
},
);
}
public async applyFPCSetupSnapshot() {
await this.snapshotManager.snapshot(
'fpc_setup',
async context => {
const feeJuiceContract = this.feeJuiceBridgeTestHarness.feeJuice;
expect(await context.pxe.isContractPubliclyDeployed(feeJuiceContract.address)).toBe(true);
const bananaCoin = this.bananaCoin;
const bananaFPC = await FPCContract.deploy(this.aliceWallet, bananaCoin.address, this.fpcAdmin)
.send()
.deployed();
this.logger.info(`BananaPay deployed at ${bananaFPC.address}`);
await this.feeJuiceBridgeTestHarness.bridgeFromL1ToL2(FEE_FUNDING_FOR_TESTER_ACCOUNT, bananaFPC.address);
return {
bananaFPCAddress: bananaFPC.address,
feeJuiceAddress: feeJuiceContract.address,
l1FeeJuiceAddress: this.feeJuiceBridgeTestHarness.l1FeeJuiceAddress,
};
},
async (data, context) => {
const bananaFPC = await FPCContract.at(data.bananaFPCAddress, this.aliceWallet);
this.bananaFPC = bananaFPC;
const logger = this.logger;
this.getBananaPublicBalanceFn = getBalancesFn('🍌.public', this.bananaCoin.methods.balance_of_public, logger);
this.getBananaPrivateBalanceFn = getBalancesFn(
'🍌.private',
this.bananaCoin.methods.balance_of_private,
logger,
);
this.getCoinbaseBalance = async () => {
const { walletClient } = createL1Clients(context.aztecNodeConfig.l1RpcUrl, MNEMONIC);
const gasL1 = getContract({
address: data.l1FeeJuiceAddress.toString(),
abi: TestERC20Abi,
client: walletClient,
});
return await gasL1.read.balanceOf([this.coinbase.toString()]);
};
},
);
}
public async applyFundAliceWithBananas() {
await this.snapshotManager.snapshot(
'fund_alice',
async () => {
await this.mintPrivateBananas(this.ALICE_INITIAL_BANANAS, this.aliceAddress);
await this.bananaCoin.methods.mint_to_public(this.aliceAddress, this.ALICE_INITIAL_BANANAS).send().wait();
},
() => Promise.resolve(),
);
}
public async applyFundAliceWithPrivateBananas() {
await this.snapshotManager.snapshot(
'fund_alice_with_private_bananas',
async () => {
await this.mintPrivateBananas(this.ALICE_INITIAL_BANANAS, this.aliceAddress);
},
() => Promise.resolve(),
);
}
public async applyFundAliceWithFeeJuice() {
await this.snapshotManager.snapshot(
'fund_alice_with_fee_juice',
async () => {
await this.mintAndBridgeFeeJuice(this.aliceAddress, FEE_FUNDING_FOR_TESTER_ACCOUNT);
},
() => Promise.resolve(),
);
}
public async applySetupSubscription() {
await this.snapshotManager.snapshot(
'setup_subscription',
async () => {
const counterContract = await CounterContract.deploy(this.bobWallet, 0, this.bobAddress).send().deployed();
// Deploy subscription contract, that allows subscriptions for SUBSCRIPTION_AMOUNT of bananas
const subscriptionContract = await AppSubscriptionContract.deploy(
this.bobWallet,
counterContract.address,
this.bobAddress,
this.bananaCoin.address,
this.SUBSCRIPTION_AMOUNT,
this.APP_SPONSORED_TX_GAS_LIMIT,
)
.send()
.deployed();
// Mint some Fee Juice to the subscription contract
// Could also use bridgeFromL1ToL2 from the harness, but this is more direct
await this.mintAndBridgeFeeJuice(subscriptionContract.address, FEE_FUNDING_FOR_TESTER_ACCOUNT);
return {
counterContractAddress: counterContract.address,
subscriptionContractAddress: subscriptionContract.address,
};
},
async ({ counterContractAddress, subscriptionContractAddress }) => {
this.counterContract = await CounterContract.at(counterContractAddress, this.bobWallet);
this.subscriptionContract = await AppSubscriptionContract.at(subscriptionContractAddress, this.bobWallet);
},
);
}
}