@aztec/ethereum 0.0.1-commit.3e3d0c9cd → 0.0.1-commit.3f296a7d2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/contracts/multicall.d.ts +51 -2
- package/dest/contracts/multicall.d.ts.map +1 -1
- package/dest/contracts/multicall.js +85 -0
- package/dest/contracts/rollup.d.ts +3 -3
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +4 -5
- package/dest/l1_artifacts.d.ts +69 -69
- package/dest/publisher_manager.d.ts +21 -7
- package/dest/publisher_manager.d.ts.map +1 -1
- package/dest/publisher_manager.js +81 -7
- package/dest/test/eth_cheat_codes.d.ts +6 -4
- package/dest/test/eth_cheat_codes.d.ts.map +1 -1
- package/dest/test/eth_cheat_codes.js +6 -4
- package/dest/test/start_anvil.d.ts +8 -1
- package/dest/test/start_anvil.d.ts.map +1 -1
- package/dest/test/start_anvil.js +16 -2
- package/package.json +5 -5
- package/src/contracts/multicall.ts +65 -1
- package/src/contracts/rollup.ts +5 -7
- package/src/publisher_manager.ts +105 -10
- package/src/test/eth_cheat_codes.ts +6 -4
- package/src/test/start_anvil.ts +25 -2
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { type LoggerBindings } from '@aztec/foundation/log';
|
|
2
2
|
import { L1TxUtils } from './l1_tx_utils/index.js';
|
|
3
3
|
export type PublisherFilter<UtilsType extends L1TxUtils> = (utils: UtilsType) => boolean;
|
|
4
|
+
/** Config accepted by PublisherManager. */
|
|
5
|
+
type PublisherManagerConfig = {
|
|
6
|
+
publisherAllowInvalidStates?: boolean;
|
|
7
|
+
publisherFundingThreshold?: bigint;
|
|
8
|
+
publisherFundingAmount?: bigint;
|
|
9
|
+
};
|
|
4
10
|
export declare class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
|
|
5
11
|
private publishers;
|
|
6
12
|
private log;
|
|
7
13
|
private config;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
private static readonly FUNDING_CHECK_INTERVAL_MS;
|
|
15
|
+
private funder?;
|
|
16
|
+
private fundingPromise?;
|
|
17
|
+
constructor(publishers: UtilsType[], config: PublisherManagerConfig, opts?: {
|
|
18
|
+
bindings?: LoggerBindings;
|
|
19
|
+
funder?: UtilsType;
|
|
20
|
+
});
|
|
21
|
+
/** Loads the state of all publishers and the funder, and starts periodic funding checks. */
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
/** Stops the funding loop and interrupts all publishers. */
|
|
24
|
+
stop(): Promise<void>;
|
|
13
25
|
getAvailablePublisher(filter?: PublisherFilter<UtilsType>): Promise<UtilsType>;
|
|
14
|
-
|
|
26
|
+
private triggerFundingIfNeeded;
|
|
27
|
+
private fundPublishers;
|
|
15
28
|
}
|
|
16
|
-
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGlzaGVyX21hbmFnZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9wdWJsaXNoZXJfbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQWUsS0FBSyxjQUFjLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFJdkYsT0FBTyxFQUFFLFNBQVMsRUFBZ0IsTUFBTSx3QkFBd0IsQ0FBQztBQXdCakUsTUFBTSxNQUFNLGVBQWUsQ0FBQyxTQUFTLFNBQVMsU0FBUyxJQUFJLENBQUMsS0FBSyxFQUFFLFNBQVMsS0FBSyxPQUFPLENBQUM7QUFFekYsMkNBQTJDO0FBQzNDLEtBQUssc0JBQXNCLEdBQUc7SUFDNUIsMkJBQTJCLENBQUMsRUFBRSxPQUFPLENBQUM7SUFDdEMseUJBQXlCLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDbkMsc0JBQXNCLENBQUMsRUFBRSxNQUFNLENBQUM7Q0FDakMsQ0FBQztBQUVGLHFCQUFhLGdCQUFnQixDQUFDLFNBQVMsU0FBUyxTQUFTLEdBQUcsU0FBUztJQVFqRSxPQUFPLENBQUMsVUFBVTtJQVBwQixPQUFPLENBQUMsR0FBRyxDQUFTO0lBQ3BCLE9BQU8sQ0FBQyxNQUFNLENBQXlCO0lBQ3ZDLE9BQU8sQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLHlCQUF5QixDQUFpQjtJQUNsRSxPQUFPLENBQUMsTUFBTSxDQUFDLENBQVk7SUFDM0IsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFpQjtJQUV4QyxZQUNVLFVBQVUsRUFBRSxTQUFTLEVBQUUsRUFDL0IsTUFBTSxFQUFFLHNCQUFzQixFQUM5QixJQUFJLENBQUMsRUFBRTtRQUFFLFFBQVEsQ0FBQyxFQUFFLGNBQWMsQ0FBQztRQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsQ0FBQTtLQUFFLEVBcUJ6RDtJQUVELDRGQUE0RjtJQUMvRSxLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQWtCbEM7SUFFRCw0REFBNEQ7SUFDL0MsSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FJakM7SUFRWSxxQkFBcUIsQ0FBQyxNQUFNLEdBQUUsZUFBZSxDQUFDLFNBQVMsQ0FBYyxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0ErQ3RHO1lBR2Esc0JBQXNCO1lBcUN0QixjQUFjO0NBVTdCIn0=
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publisher_manager.d.ts","sourceRoot":"","sources":["../src/publisher_manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"publisher_manager.d.ts","sourceRoot":"","sources":["../src/publisher_manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AAIvF,OAAO,EAAE,SAAS,EAAgB,MAAM,wBAAwB,CAAC;AAwBjE,MAAM,MAAM,eAAe,CAAC,SAAS,SAAS,SAAS,IAAI,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;AAEzF,2CAA2C;AAC3C,KAAK,sBAAsB,GAAG;IAC5B,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,qBAAa,gBAAgB,CAAC,SAAS,SAAS,SAAS,GAAG,SAAS;IAQjE,OAAO,CAAC,UAAU;IAPpB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAiB;IAClE,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,cAAc,CAAC,CAAiB;IAExC,YACU,UAAU,EAAE,SAAS,EAAE,EAC/B,MAAM,EAAE,sBAAsB,EAC9B,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,EAqBzD;IAED,4FAA4F;IAC/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBlC;IAED,4DAA4D;IAC/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjC;IAQY,qBAAqB,CAAC,MAAM,GAAE,eAAe,CAAC,SAAS,CAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CA+CtG;YAGa,sBAAsB;YAqCtB,cAAc;CAU7B"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { pick } from '@aztec/foundation/collection';
|
|
2
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
4
|
+
import { Multicall3 } from './contracts/multicall.js';
|
|
3
5
|
import { TxUtilsState } from './l1_tx_utils/index.js';
|
|
4
6
|
// Defines the order in which we prioritise publishers based on their state (first is better)
|
|
5
7
|
const sortOrder = [
|
|
@@ -25,15 +27,43 @@ export class PublisherManager {
|
|
|
25
27
|
publishers;
|
|
26
28
|
log;
|
|
27
29
|
config;
|
|
28
|
-
|
|
30
|
+
static FUNDING_CHECK_INTERVAL_MS = 2 * 60 * 1000;
|
|
31
|
+
funder;
|
|
32
|
+
fundingPromise;
|
|
33
|
+
constructor(publishers, config, opts){
|
|
29
34
|
this.publishers = publishers;
|
|
30
|
-
this.
|
|
35
|
+
this.funder = opts?.funder;
|
|
36
|
+
this.log = createLogger('publisher:manager', opts?.bindings);
|
|
31
37
|
this.log.info(`PublisherManager initialized with ${publishers.length} publishers.`);
|
|
32
38
|
this.publishers = publishers;
|
|
33
|
-
this.config = pick(config, 'publisherAllowInvalidStates');
|
|
39
|
+
this.config = pick(config, 'publisherAllowInvalidStates', 'publisherFundingThreshold', 'publisherFundingAmount');
|
|
40
|
+
const hasThreshold = this.config.publisherFundingThreshold !== undefined;
|
|
41
|
+
const hasAmount = this.config.publisherFundingAmount !== undefined;
|
|
42
|
+
if (hasThreshold !== hasAmount) {
|
|
43
|
+
this.log.warn(`Incomplete funding config: both publisherFundingThreshold and publisherFundingAmount must be set`);
|
|
44
|
+
}
|
|
45
|
+
if (this.funder) {
|
|
46
|
+
const funderAddress = this.funder.getSenderAddress();
|
|
47
|
+
if (publishers.some((p)=>p.getSenderAddress().equals(funderAddress))) {
|
|
48
|
+
this.log.error(`Funding account ${funderAddress} is also a publisher, disabling funding to avoid self-funding`);
|
|
49
|
+
this.funder = undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Loads the state of all publishers and the funder, and starts periodic funding checks. */ async start() {
|
|
54
|
+
await Promise.all([
|
|
55
|
+
...this.publishers.map((pub)=>pub.loadStateAndResumeMonitoring()),
|
|
56
|
+
this.funder?.loadStateAndResumeMonitoring()
|
|
57
|
+
]);
|
|
58
|
+
if (this.funder && this.config.publisherFundingThreshold !== undefined && this.config.publisherFundingAmount !== undefined) {
|
|
59
|
+
this.fundingPromise = new RunningPromise(()=>this.triggerFundingIfNeeded(), this.log, PublisherManager.FUNDING_CHECK_INTERVAL_MS);
|
|
60
|
+
this.fundingPromise.start();
|
|
61
|
+
}
|
|
34
62
|
}
|
|
35
|
-
/**
|
|
36
|
-
await
|
|
63
|
+
/** Stops the funding loop and interrupts all publishers. */ async stop() {
|
|
64
|
+
await this.fundingPromise?.stop();
|
|
65
|
+
this.publishers.forEach((pub)=>pub.interrupt());
|
|
66
|
+
this.funder?.interrupt();
|
|
37
67
|
}
|
|
38
68
|
// Finds and prioritises available publishers based on
|
|
39
69
|
// 1. Validity as per the provided filter function
|
|
@@ -82,7 +112,51 @@ export class PublisherManager {
|
|
|
82
112
|
});
|
|
83
113
|
return sortedPublishers[0].publisher;
|
|
84
114
|
}
|
|
85
|
-
|
|
86
|
-
this
|
|
115
|
+
/** Check all publisher balances and fund those below threshold. */ async triggerFundingIfNeeded() {
|
|
116
|
+
const { funder, config } = this;
|
|
117
|
+
if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const allBalances = await Promise.all(this.publishers.map(async (pub)=>({
|
|
121
|
+
balance: await pub.getSenderBalance(),
|
|
122
|
+
publisher: pub
|
|
123
|
+
})));
|
|
124
|
+
const lowBalance = allBalances.filter((p)=>p.balance < config.publisherFundingThreshold);
|
|
125
|
+
if (lowBalance.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const fundingAmount = config.publisherFundingAmount;
|
|
129
|
+
const funderBalance = await funder.getSenderBalance();
|
|
130
|
+
if (funderBalance < 10n * fundingAmount) {
|
|
131
|
+
this.log.warn(`Funding account balance is low`, {
|
|
132
|
+
funderBalance,
|
|
133
|
+
threshold: 10n * fundingAmount
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const affordableCount = Number(funderBalance / fundingAmount);
|
|
137
|
+
if (affordableCount === 0) {
|
|
138
|
+
this.log.error(`Funding account balance too low to fund any publisher`, {
|
|
139
|
+
funderBalance,
|
|
140
|
+
fundingAmount
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (affordableCount < lowBalance.length) {
|
|
145
|
+
this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, {
|
|
146
|
+
funderBalance,
|
|
147
|
+
fundingAmount
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const toFund = lowBalance.slice(0, affordableCount).map((p)=>p.publisher);
|
|
151
|
+
await this.fundPublishers(toFund);
|
|
152
|
+
}
|
|
153
|
+
/** Fund publishers via a single Multicall3 aggregate3Value transaction. */ async fundPublishers(publishers) {
|
|
154
|
+
const fundingAmount = this.config.publisherFundingAmount;
|
|
155
|
+
const calls = publishers.map((pub)=>({
|
|
156
|
+
to: pub.getSenderAddress().toString(),
|
|
157
|
+
value: fundingAmount
|
|
158
|
+
}));
|
|
159
|
+
await Multicall3.forwardValue(calls, this.funder, this.log);
|
|
160
|
+
this.log.info(`Funded ${publishers.length} publishers`);
|
|
87
161
|
}
|
|
88
162
|
}
|
|
@@ -58,10 +58,12 @@ export declare class EthCheatCodes {
|
|
|
58
58
|
*/
|
|
59
59
|
chainId(): Promise<number>;
|
|
60
60
|
/**
|
|
61
|
-
* Get the
|
|
62
|
-
*
|
|
61
|
+
* Get the timestamp of the latest mined L1 block.
|
|
62
|
+
* Note: this is NOT the current time — it's the discrete timestamp of the last block.
|
|
63
|
+
* Between blocks, the actual chain time advances but no new block reflects it.
|
|
64
|
+
* @returns The latest block timestamp in seconds
|
|
63
65
|
*/
|
|
64
|
-
|
|
66
|
+
lastBlockTimestamp(): Promise<number>;
|
|
65
67
|
/**
|
|
66
68
|
* Advance the chain by a number of blocks
|
|
67
69
|
* @param numberOfBlocks - The number of blocks to mine
|
|
@@ -226,4 +228,4 @@ export type TxPoolTransaction = Transaction & {
|
|
|
226
228
|
poolState: TxPoolState;
|
|
227
229
|
};
|
|
228
230
|
export {};
|
|
229
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
231
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXRoX2NoZWF0X2NvZGVzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVzdC9ldGhfY2hlYXRfY29kZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBSTNELE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRTlFLE9BQU8sRUFBRSxLQUFLLEtBQUssRUFBRSxLQUFLLEdBQUcsRUFBRSxLQUFLLFdBQVcsRUFBbUQsTUFBTSxNQUFNLENBQUM7QUFHL0csT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFcEQ7O0dBRUc7QUFDSCxxQkFBYSxhQUFhO0lBR3RCOztPQUVHO0lBQ0ksT0FBTyxFQUFFLE1BQU0sRUFBRTtJQUN4Qjs7T0FFRztJQUNJLFlBQVksRUFBRSxZQUFZLEdBQUcsZ0JBQWdCO0lBQ3BEOztPQUVHO0lBQ0ksTUFBTTtJQUNiOztPQUVHO0lBQ0ksS0FBSyxFQUFFLEtBQUs7SUFqQnJCLFNBQWdCLFlBQVksRUFBRSxnQkFBZ0IsQ0FBQztJQUMvQztJQUNFOztPQUVHO0lBQ0ksT0FBTyxFQUFFLE1BQU0sRUFBRTtJQUN4Qjs7T0FFRztJQUNJLFlBQVksRUFBRSxZQUFZLEdBQUcsZ0JBQWdCO0lBQ3BEOztPQUVHO0lBQ0ksTUFBTSx5Q0FBdUM7SUFDcEQ7O09BRUc7SUFDSSxLQUFLLEdBQUUsS0FBZSxFQU05QjtJQUVNLE9BQU8sQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsZ0JBRzNDO1lBRWEsU0FBUztJQU92Qjs7O09BR0c7SUFDVSxZQUFZLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQVE1QztJQUVEOzs7T0FHRztJQUNVLFdBQVcsSUFBSSxPQUFPLENBQUMsTUFBTSxDQUFDLENBRzFDO0lBRUQ7OztPQUdHO0lB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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eth_cheat_codes.d.ts","sourceRoot":"","sources":["../../src/test/eth_cheat_codes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAI3D,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE9E,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,EAAmD,MAAM,MAAM,CAAC;AAG/G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;GAEG;AACH,qBAAa,aAAa;IAGtB;;OAEG;IACI,OAAO,EAAE,MAAM,EAAE;IACxB;;OAEG;IACI,YAAY,EAAE,YAAY,GAAG,gBAAgB;IACpD;;OAEG;IACI,MAAM;IACb;;OAEG;IACI,KAAK,EAAE,KAAK;IAjBrB,SAAgB,YAAY,EAAE,gBAAgB,CAAC;IAC/C;IACE;;OAEG;IACI,OAAO,EAAE,MAAM,EAAE;IACxB;;OAEG;IACI,YAAY,EAAE,YAAY,GAAG,gBAAgB;IACpD;;OAEG;IACI,MAAM,yCAAuC;IACpD;;OAEG;IACI,KAAK,GAAE,KAAe,EAM9B;IAEM,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,gBAG3C;YAEa,SAAS;IAOvB;;;OAGG;IACU,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAQ5C;IAED;;;OAGG;IACU,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAG1C;IAED;;;OAGG;IACU,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAGtC;IAED
|
|
1
|
+
{"version":3,"file":"eth_cheat_codes.d.ts","sourceRoot":"","sources":["../../src/test/eth_cheat_codes.ts"],"names":[],"mappings":"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"}
|
|
@@ -70,9 +70,11 @@ import { foundry } from 'viem/chains';
|
|
|
70
70
|
return parseInt(res, 16);
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
* Get the
|
|
74
|
-
*
|
|
75
|
-
|
|
73
|
+
* Get the timestamp of the latest mined L1 block.
|
|
74
|
+
* Note: this is NOT the current time — it's the discrete timestamp of the last block.
|
|
75
|
+
* Between blocks, the actual chain time advances but no new block reflects it.
|
|
76
|
+
* @returns The latest block timestamp in seconds
|
|
77
|
+
*/ async lastBlockTimestamp() {
|
|
76
78
|
const res = await this.doRpcCall('eth_getBlockByNumber', [
|
|
77
79
|
'latest',
|
|
78
80
|
true
|
|
@@ -536,7 +538,7 @@ import { foundry } from 'viem/chains';
|
|
|
536
538
|
}
|
|
537
539
|
}
|
|
538
540
|
async syncDateProvider() {
|
|
539
|
-
const timestamp = await this.
|
|
541
|
+
const timestamp = await this.lastBlockTimestamp();
|
|
540
542
|
if ('setTime' in this.dateProvider) {
|
|
541
543
|
this.dateProvider.setTime(timestamp * 1000);
|
|
542
544
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TestDateProvider } from '@aztec/foundation/timer';
|
|
1
2
|
/** Minimal interface matching the @viem/anvil Anvil shape used by callers. */
|
|
2
3
|
export interface Anvil {
|
|
3
4
|
readonly port: number;
|
|
@@ -24,10 +25,16 @@ export declare function startAnvil(opts?: {
|
|
|
24
25
|
* L1-finality-based logic work without needing hundreds of mined blocks.
|
|
25
26
|
*/
|
|
26
27
|
slotsInAnEpoch?: number;
|
|
28
|
+
/**
|
|
29
|
+
* If provided, the date provider will be synced to anvil's block time on every mined block.
|
|
30
|
+
* This keeps the dateProvider in lockstep with anvil's chain time, avoiding drift between
|
|
31
|
+
* the wall clock and the L1 chain when computing L1 slot timestamps.
|
|
32
|
+
*/
|
|
33
|
+
dateProvider?: TestDateProvider;
|
|
27
34
|
}): Promise<{
|
|
28
35
|
anvil: Anvil;
|
|
29
36
|
methodCalls?: string[];
|
|
30
37
|
rpcUrl: string;
|
|
31
38
|
stop: () => Promise<void>;
|
|
32
39
|
}>;
|
|
33
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
40
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhcnRfYW52aWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3N0YXJ0X2FudmlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFNaEUsOEVBQThFO0FBQzlFLE1BQU0sV0FBVyxLQUFLO0lBQ3BCLFFBQVEsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLFFBQVEsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO0lBQ3RCLFFBQVEsQ0FBQyxNQUFNLEVBQUUsV0FBVyxHQUFHLE1BQU0sQ0FBQztJQUN0QyxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0NBQ3ZCO0FBRUQ7O0dBRUc7QUFDSCx3QkFBc0IsVUFBVSxDQUM5QixJQUFJLEdBQUU7SUFDSixJQUFJLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDZCxXQUFXLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDckIsR0FBRyxDQUFDLEVBQUUsT0FBTyxDQUFDO0lBQ2Qsa0JBQWtCLENBQUMsRUFBRSxPQUFPLENBQUM7SUFDN0IsUUFBUSxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ2xCLE9BQU8sQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUNqQixxREFBcUQ7SUFDckQsUUFBUSxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ2xCOzs7OztPQUtHO0lBQ0gsY0FBYyxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ3hCOzs7O09BSUc7SUFDSCxZQUFZLENBQUMsRUFBRSxnQkFBZ0IsQ0FBQztDQUM1QixHQUNMLE9BQU8sQ0FBQztJQUFFLEtBQUssRUFBRSxLQUFLLENBQUM7SUFBQyxXQUFXLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUFDLE1BQU0sRUFBRSxNQUFNLENBQUM7SUFBQyxJQUFJLEVBQUUsTUFBTSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7Q0FBRSxDQUFDLENBK0g5RiJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start_anvil.d.ts","sourceRoot":"","sources":["../../src/test/start_anvil.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"start_anvil.d.ts","sourceRoot":"","sources":["../../src/test/start_anvil.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAMhE,8EAA8E;AAC9E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,IAAI,GAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;CAC5B,GACL,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA+H9F"}
|
package/dest/test/start_anvil.js
CHANGED
|
@@ -75,12 +75,15 @@ import { dirname, resolve } from 'path';
|
|
|
75
75
|
child.stderr?.on('data', onStderr);
|
|
76
76
|
child.once('close', onClose);
|
|
77
77
|
});
|
|
78
|
-
// Continue piping for logging
|
|
79
|
-
if (logger || opts.captureMethodCalls) {
|
|
78
|
+
// Continue piping for logging, method-call capture, and/or dateProvider sync after startup.
|
|
79
|
+
if (logger || opts.captureMethodCalls || opts.dateProvider) {
|
|
80
80
|
child.stdout?.on('data', (data)=>{
|
|
81
81
|
const text = data.toString();
|
|
82
82
|
logger?.debug(text.trim());
|
|
83
83
|
methodCalls?.push(...text.match(/eth_[^\s]+/g) || []);
|
|
84
|
+
if (opts.dateProvider) {
|
|
85
|
+
syncDateProviderFromAnvilOutput(text, opts.dateProvider);
|
|
86
|
+
}
|
|
84
87
|
});
|
|
85
88
|
child.stderr?.on('data', (data)=>{
|
|
86
89
|
logger?.debug(data.toString().trim());
|
|
@@ -125,6 +128,17 @@ import { dirname, resolve } from 'path';
|
|
|
125
128
|
rpcUrl: `http://127.0.0.1:${port}`
|
|
126
129
|
};
|
|
127
130
|
}
|
|
131
|
+
/** Extracts block time from anvil stdout and syncs the dateProvider. */ function syncDateProviderFromAnvilOutput(text, dateProvider) {
|
|
132
|
+
// Anvil logs mined blocks as:
|
|
133
|
+
// Block Time: "Fri, 20 Mar 2026 02:10:46 +0000"
|
|
134
|
+
const match = text.match(/Block Time:\s*"([^"]+)"/);
|
|
135
|
+
if (match) {
|
|
136
|
+
const blockTimeMs = new Date(match[1]).getTime();
|
|
137
|
+
if (!isNaN(blockTimeMs)) {
|
|
138
|
+
dateProvider.setTime(blockTimeMs);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
128
142
|
/** Send SIGTERM, wait up to 5 s, then SIGKILL. All timers are always cleared. */ function killChild(child) {
|
|
129
143
|
return new Promise((resolve)=>{
|
|
130
144
|
if (child.exitCode !== null || child.killed) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/ethereum",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.3f296a7d2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./account": "./dest/account.js",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"../package.common.json"
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
54
|
-
"@aztec/constants": "0.0.1-commit.
|
|
55
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
56
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
53
|
+
"@aztec/blob-lib": "0.0.1-commit.3f296a7d2",
|
|
54
|
+
"@aztec/constants": "0.0.1-commit.3f296a7d2",
|
|
55
|
+
"@aztec/foundation": "0.0.1-commit.3f296a7d2",
|
|
56
|
+
"@aztec/l1-artifacts": "0.0.1-commit.3f296a7d2",
|
|
57
57
|
"dotenv": "^16.0.3",
|
|
58
58
|
"lodash.chunk": "^4.2.0",
|
|
59
59
|
"lodash.pickby": "^4.5.0",
|
|
@@ -2,7 +2,7 @@ import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
|
2
2
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
3
3
|
import type { Logger } from '@aztec/foundation/log';
|
|
4
4
|
|
|
5
|
-
import { type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multicall3Abi } from 'viem';
|
|
5
|
+
import { type Address, type EncodeFunctionDataParameters, type Hex, encodeFunctionData, multicall3Abi } from 'viem';
|
|
6
6
|
|
|
7
7
|
import type { L1BlobInputs, L1TxConfig, L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
|
|
8
8
|
import type { ExtendedViemWalletClient } from '../types.js';
|
|
@@ -11,6 +11,39 @@ import { RollupContract } from './rollup.js';
|
|
|
11
11
|
|
|
12
12
|
export const MULTI_CALL_3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' as const;
|
|
13
13
|
|
|
14
|
+
/** ABI fragment for aggregate3Value — not included in viem's multicall3Abi. */
|
|
15
|
+
export const aggregate3ValueAbi = [
|
|
16
|
+
{
|
|
17
|
+
inputs: [
|
|
18
|
+
{
|
|
19
|
+
components: [
|
|
20
|
+
{ internalType: 'address', name: 'target', type: 'address' },
|
|
21
|
+
{ internalType: 'bool', name: 'allowFailure', type: 'bool' },
|
|
22
|
+
{ internalType: 'uint256', name: 'value', type: 'uint256' },
|
|
23
|
+
{ internalType: 'bytes', name: 'callData', type: 'bytes' },
|
|
24
|
+
],
|
|
25
|
+
internalType: 'struct Multicall3.Call3Value[]',
|
|
26
|
+
name: 'calls',
|
|
27
|
+
type: 'tuple[]',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
name: 'aggregate3Value',
|
|
31
|
+
outputs: [
|
|
32
|
+
{
|
|
33
|
+
components: [
|
|
34
|
+
{ internalType: 'bool', name: 'success', type: 'bool' },
|
|
35
|
+
{ internalType: 'bytes', name: 'returnData', type: 'bytes' },
|
|
36
|
+
],
|
|
37
|
+
internalType: 'struct Multicall3.Result[]',
|
|
38
|
+
name: 'returnData',
|
|
39
|
+
type: 'tuple[]',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
stateMutability: 'payable',
|
|
43
|
+
type: 'function',
|
|
44
|
+
},
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
14
47
|
export class Multicall3 {
|
|
15
48
|
static async forward(
|
|
16
49
|
requests: L1TxRequest[],
|
|
@@ -122,6 +155,37 @@ export class Multicall3 {
|
|
|
122
155
|
throw err;
|
|
123
156
|
}
|
|
124
157
|
}
|
|
158
|
+
|
|
159
|
+
/** Batch multiple value transfers into a single aggregate3Value call on Multicall3. */
|
|
160
|
+
static async forwardValue(calls: { to: Address; value: bigint }[], l1TxUtils: L1TxUtils, logger: Logger) {
|
|
161
|
+
const args = calls.map(c => ({
|
|
162
|
+
target: c.to,
|
|
163
|
+
allowFailure: false,
|
|
164
|
+
value: c.value,
|
|
165
|
+
callData: '0x' as Hex,
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const data = encodeFunctionData({
|
|
169
|
+
abi: aggregate3ValueAbi,
|
|
170
|
+
functionName: 'aggregate3Value',
|
|
171
|
+
args: [args],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const totalValue = calls.reduce((sum, c) => sum + c.value, 0n);
|
|
175
|
+
|
|
176
|
+
logger.info(`Sending aggregate3Value with ${calls.length} calls`, { totalValue });
|
|
177
|
+
const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
|
|
178
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
179
|
+
data,
|
|
180
|
+
value: totalValue,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (receipt.status !== 'success') {
|
|
184
|
+
throw new Error(`aggregate3Value transaction reverted: ${receipt.transactionHash}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { receipt };
|
|
188
|
+
}
|
|
125
189
|
}
|
|
126
190
|
|
|
127
191
|
export async function deployMulticall3(l1Client: ExtendedViemWalletClient, logger: Logger) {
|
package/src/contracts/rollup.ts
CHANGED
|
@@ -778,14 +778,13 @@ export class RollupContract {
|
|
|
778
778
|
* timestamp of the next L1 block
|
|
779
779
|
* @throws otherwise
|
|
780
780
|
*/
|
|
781
|
-
public async
|
|
781
|
+
public async canProposeAt(
|
|
782
782
|
archive: Buffer,
|
|
783
783
|
account: `0x${string}` | Account,
|
|
784
|
-
|
|
784
|
+
timestamp: bigint,
|
|
785
785
|
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
786
786
|
): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
|
|
787
|
-
const
|
|
788
|
-
const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
|
|
787
|
+
const timeOfNextL1Slot = timestamp;
|
|
789
788
|
const who = typeof account === 'string' ? account : account.address;
|
|
790
789
|
|
|
791
790
|
try {
|
|
@@ -938,11 +937,10 @@ export class RollupContract {
|
|
|
938
937
|
return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
|
|
939
938
|
}
|
|
940
939
|
|
|
941
|
-
async getAttesters(): Promise<EthAddress[]> {
|
|
940
|
+
async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
|
|
942
941
|
const attesterSize = await this.getActiveAttesterCount();
|
|
943
942
|
const gse = new GSEContract(this.client, await this.getGSE());
|
|
944
|
-
const ts = (await this.client.getBlock()).timestamp;
|
|
945
|
-
|
|
943
|
+
const ts = timestamp ?? (await this.client.getBlock()).timestamp;
|
|
946
944
|
const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
|
|
947
945
|
const chunks = chunk(indices, 1000);
|
|
948
946
|
|
package/src/publisher_manager.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { pick } from '@aztec/foundation/collection';
|
|
2
2
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
3
4
|
|
|
5
|
+
import { Multicall3 } from './contracts/multicall.js';
|
|
4
6
|
import { L1TxUtils, TxUtilsState } from './l1_tx_utils/index.js';
|
|
5
7
|
|
|
6
8
|
// Defines the order in which we prioritise publishers based on their state (first is better)
|
|
@@ -27,24 +29,72 @@ const busyStates: TxUtilsState[] = [
|
|
|
27
29
|
|
|
28
30
|
export type PublisherFilter<UtilsType extends L1TxUtils> = (utils: UtilsType) => boolean;
|
|
29
31
|
|
|
32
|
+
/** Config accepted by PublisherManager. */
|
|
33
|
+
type PublisherManagerConfig = {
|
|
34
|
+
publisherAllowInvalidStates?: boolean;
|
|
35
|
+
publisherFundingThreshold?: bigint;
|
|
36
|
+
publisherFundingAmount?: bigint;
|
|
37
|
+
};
|
|
38
|
+
|
|
30
39
|
export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
|
|
31
40
|
private log: Logger;
|
|
32
|
-
private config:
|
|
41
|
+
private config: PublisherManagerConfig;
|
|
42
|
+
private static readonly FUNDING_CHECK_INTERVAL_MS = 2 * 60 * 1000;
|
|
43
|
+
private funder?: UtilsType;
|
|
44
|
+
private fundingPromise?: RunningPromise;
|
|
33
45
|
|
|
34
46
|
constructor(
|
|
35
47
|
private publishers: UtilsType[],
|
|
36
|
-
config:
|
|
37
|
-
bindings?: LoggerBindings,
|
|
48
|
+
config: PublisherManagerConfig,
|
|
49
|
+
opts?: { bindings?: LoggerBindings; funder?: UtilsType },
|
|
38
50
|
) {
|
|
39
|
-
this.
|
|
51
|
+
this.funder = opts?.funder;
|
|
52
|
+
this.log = createLogger('publisher:manager', opts?.bindings);
|
|
40
53
|
this.log.info(`PublisherManager initialized with ${publishers.length} publishers.`);
|
|
41
54
|
this.publishers = publishers;
|
|
42
|
-
this.config = pick(config, 'publisherAllowInvalidStates');
|
|
55
|
+
this.config = pick(config, 'publisherAllowInvalidStates', 'publisherFundingThreshold', 'publisherFundingAmount');
|
|
56
|
+
|
|
57
|
+
const hasThreshold = this.config.publisherFundingThreshold !== undefined;
|
|
58
|
+
const hasAmount = this.config.publisherFundingAmount !== undefined;
|
|
59
|
+
if (hasThreshold !== hasAmount) {
|
|
60
|
+
this.log.warn(`Incomplete funding config: both publisherFundingThreshold and publisherFundingAmount must be set`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.funder) {
|
|
64
|
+
const funderAddress = this.funder.getSenderAddress();
|
|
65
|
+
if (publishers.some(p => p.getSenderAddress().equals(funderAddress))) {
|
|
66
|
+
this.log.error(`Funding account ${funderAddress} is also a publisher, disabling funding to avoid self-funding`);
|
|
67
|
+
this.funder = undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
43
70
|
}
|
|
44
71
|
|
|
45
|
-
/** Loads the state of all publishers and
|
|
46
|
-
public async
|
|
47
|
-
await Promise.all(
|
|
72
|
+
/** Loads the state of all publishers and the funder, and starts periodic funding checks. */
|
|
73
|
+
public async start(): Promise<void> {
|
|
74
|
+
await Promise.all([
|
|
75
|
+
...this.publishers.map(pub => pub.loadStateAndResumeMonitoring()),
|
|
76
|
+
this.funder?.loadStateAndResumeMonitoring(),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
this.funder &&
|
|
81
|
+
this.config.publisherFundingThreshold !== undefined &&
|
|
82
|
+
this.config.publisherFundingAmount !== undefined
|
|
83
|
+
) {
|
|
84
|
+
this.fundingPromise = new RunningPromise(
|
|
85
|
+
() => this.triggerFundingIfNeeded(),
|
|
86
|
+
this.log,
|
|
87
|
+
PublisherManager.FUNDING_CHECK_INTERVAL_MS,
|
|
88
|
+
);
|
|
89
|
+
this.fundingPromise.start();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Stops the funding loop and interrupts all publishers. */
|
|
94
|
+
public async stop(): Promise<void> {
|
|
95
|
+
await this.fundingPromise?.stop();
|
|
96
|
+
this.publishers.forEach(pub => pub.interrupt());
|
|
97
|
+
this.funder?.interrupt();
|
|
48
98
|
}
|
|
49
99
|
|
|
50
100
|
// Finds and prioritises available publishers based on
|
|
@@ -102,7 +152,52 @@ export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
|
|
|
102
152
|
return sortedPublishers[0].publisher;
|
|
103
153
|
}
|
|
104
154
|
|
|
105
|
-
|
|
106
|
-
|
|
155
|
+
/** Check all publisher balances and fund those below threshold. */
|
|
156
|
+
private async triggerFundingIfNeeded(): Promise<void> {
|
|
157
|
+
const { funder, config } = this;
|
|
158
|
+
if (!funder || config.publisherFundingThreshold === undefined || config.publisherFundingAmount === undefined) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const allBalances = await Promise.all(
|
|
163
|
+
this.publishers.map(async pub => ({ balance: await pub.getSenderBalance(), publisher: pub })),
|
|
164
|
+
);
|
|
165
|
+
const lowBalance = allBalances.filter(p => p.balance < config.publisherFundingThreshold!);
|
|
166
|
+
if (lowBalance.length === 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const fundingAmount = config.publisherFundingAmount!;
|
|
171
|
+
const funderBalance = await funder.getSenderBalance();
|
|
172
|
+
|
|
173
|
+
if (funderBalance < 10n * fundingAmount) {
|
|
174
|
+
this.log.warn(`Funding account balance is low`, { funderBalance, threshold: 10n * fundingAmount });
|
|
175
|
+
}
|
|
176
|
+
const affordableCount = Number(funderBalance / fundingAmount);
|
|
177
|
+
if (affordableCount === 0) {
|
|
178
|
+
this.log.error(`Funding account balance too low to fund any publisher`, { funderBalance, fundingAmount });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (affordableCount < lowBalance.length) {
|
|
182
|
+
this.log.warn(`Funder can only afford ${affordableCount}/${lowBalance.length} publishers`, {
|
|
183
|
+
funderBalance,
|
|
184
|
+
fundingAmount,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const toFund = lowBalance.slice(0, affordableCount).map(p => p.publisher);
|
|
189
|
+
await this.fundPublishers(toFund);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Fund publishers via a single Multicall3 aggregate3Value transaction. */
|
|
193
|
+
private async fundPublishers(publishers: UtilsType[]): Promise<void> {
|
|
194
|
+
const fundingAmount = this.config.publisherFundingAmount!;
|
|
195
|
+
const calls = publishers.map(pub => ({
|
|
196
|
+
to: pub.getSenderAddress().toString(),
|
|
197
|
+
value: fundingAmount,
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
await Multicall3.forwardValue(calls, this.funder!, this.log);
|
|
201
|
+
this.log.info(`Funded ${publishers.length} publishers`);
|
|
107
202
|
}
|
|
108
203
|
}
|
|
@@ -85,10 +85,12 @@ export class EthCheatCodes {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Get the
|
|
89
|
-
*
|
|
88
|
+
* Get the timestamp of the latest mined L1 block.
|
|
89
|
+
* Note: this is NOT the current time — it's the discrete timestamp of the last block.
|
|
90
|
+
* Between blocks, the actual chain time advances but no new block reflects it.
|
|
91
|
+
* @returns The latest block timestamp in seconds
|
|
90
92
|
*/
|
|
91
|
-
public async
|
|
93
|
+
public async lastBlockTimestamp(): Promise<number> {
|
|
92
94
|
const res = await this.doRpcCall('eth_getBlockByNumber', ['latest', true]);
|
|
93
95
|
return parseInt(res.timestamp, 16);
|
|
94
96
|
}
|
|
@@ -552,7 +554,7 @@ export class EthCheatCodes {
|
|
|
552
554
|
}
|
|
553
555
|
|
|
554
556
|
public async syncDateProvider() {
|
|
555
|
-
const timestamp = await this.
|
|
557
|
+
const timestamp = await this.lastBlockTimestamp();
|
|
556
558
|
if ('setTime' in this.dateProvider) {
|
|
557
559
|
this.dateProvider.setTime(timestamp * 1000);
|
|
558
560
|
}
|