@aztec/ethereum 0.0.0-test.0
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/README.md +3 -0
- package/dest/chain.d.ts +25 -0
- package/dest/chain.d.ts.map +1 -0
- package/dest/chain.js +53 -0
- package/dest/client.d.ts +16 -0
- package/dest/client.d.ts.map +1 -0
- package/dest/client.js +31 -0
- package/dest/config.d.ts +39 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +70 -0
- package/dest/constants.d.ts +4 -0
- package/dest/constants.d.ts.map +1 -0
- package/dest/constants.js +2 -0
- package/dest/contracts/empire_base.d.ts +13 -0
- package/dest/contracts/empire_base.d.ts.map +1 -0
- package/dest/contracts/empire_base.js +11 -0
- package/dest/contracts/fee_juice.d.ts +15 -0
- package/dest/contracts/fee_juice.d.ts.map +1 -0
- package/dest/contracts/fee_juice.js +52 -0
- package/dest/contracts/forwarder.d.ts +24 -0
- package/dest/contracts/forwarder.d.ts.map +1 -0
- package/dest/contracts/forwarder.js +101 -0
- package/dest/contracts/governance.d.ts +79 -0
- package/dest/contracts/governance.d.ts.map +1 -0
- package/dest/contracts/governance.js +247 -0
- package/dest/contracts/governance_proposer.d.ts +28 -0
- package/dest/contracts/governance_proposer.d.ts.map +1 -0
- package/dest/contracts/governance_proposer.js +82 -0
- package/dest/contracts/index.d.ts +9 -0
- package/dest/contracts/index.d.ts.map +1 -0
- package/dest/contracts/index.js +8 -0
- package/dest/contracts/registry.d.ts +24 -0
- package/dest/contracts/registry.d.ts.map +1 -0
- package/dest/contracts/registry.js +85 -0
- package/dest/contracts/rollup.d.ts +92 -0
- package/dest/contracts/rollup.d.ts.map +1 -0
- package/dest/contracts/rollup.js +234 -0
- package/dest/contracts/slashing_proposer.d.ts +21 -0
- package/dest/contracts/slashing_proposer.d.ts.map +1 -0
- package/dest/contracts/slashing_proposer.js +47 -0
- package/dest/deploy_l1_contracts.d.ts +21210 -0
- package/dest/deploy_l1_contracts.d.ts.map +1 -0
- package/dest/deploy_l1_contracts.js +687 -0
- package/dest/eth_cheat_codes.d.ts +147 -0
- package/dest/eth_cheat_codes.d.ts.map +1 -0
- package/dest/eth_cheat_codes.js +303 -0
- package/dest/index.d.ts +14 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +13 -0
- package/dest/l1_contract_addresses.d.ts +57 -0
- package/dest/l1_contract_addresses.d.ts.map +1 -0
- package/dest/l1_contract_addresses.js +97 -0
- package/dest/l1_reader.d.ts +16 -0
- package/dest/l1_reader.d.ts.map +1 -0
- package/dest/l1_reader.js +27 -0
- package/dest/l1_tx_utils.d.ts +192 -0
- package/dest/l1_tx_utils.d.ts.map +1 -0
- package/dest/l1_tx_utils.js +641 -0
- package/dest/l1_tx_utils_with_blobs.d.ts +12 -0
- package/dest/l1_tx_utils_with_blobs.d.ts.map +1 -0
- package/dest/l1_tx_utils_with_blobs.js +64 -0
- package/dest/queries.d.ts +12 -0
- package/dest/queries.d.ts.map +1 -0
- package/dest/queries.js +35 -0
- package/dest/test/delayed_tx_utils.d.ts +8 -0
- package/dest/test/delayed_tx_utils.d.ts.map +1 -0
- package/dest/test/delayed_tx_utils.js +21 -0
- package/dest/test/eth_cheat_codes_with_state.d.ts +18 -0
- package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -0
- package/dest/test/eth_cheat_codes_with_state.js +34 -0
- package/dest/test/index.d.ts +6 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +5 -0
- package/dest/test/start_anvil.d.ts +12 -0
- package/dest/test/start_anvil.d.ts.map +1 -0
- package/dest/test/start_anvil.js +46 -0
- package/dest/test/tx_delayer.d.ts +25 -0
- package/dest/test/tx_delayer.d.ts.map +1 -0
- package/dest/test/tx_delayer.js +116 -0
- package/dest/test/upgrade_utils.d.ts +11 -0
- package/dest/test/upgrade_utils.d.ts.map +1 -0
- package/dest/test/upgrade_utils.js +104 -0
- package/dest/types.d.ts +14 -0
- package/dest/types.d.ts.map +1 -0
- package/dest/types.js +1 -0
- package/dest/utils.d.ts +24 -0
- package/dest/utils.d.ts.map +1 -0
- package/dest/utils.js +209 -0
- package/package.json +98 -0
- package/src/chain.ts +71 -0
- package/src/client.ts +58 -0
- package/src/config.ts +103 -0
- package/src/constants.ts +4 -0
- package/src/contracts/empire_base.ts +19 -0
- package/src/contracts/fee_juice.ts +43 -0
- package/src/contracts/forwarder.ts +132 -0
- package/src/contracts/governance.ts +285 -0
- package/src/contracts/governance_proposer.ts +82 -0
- package/src/contracts/index.ts +8 -0
- package/src/contracts/registry.ts +106 -0
- package/src/contracts/rollup.ts +274 -0
- package/src/contracts/slashing_proposer.ts +51 -0
- package/src/deploy_l1_contracts.ts +948 -0
- package/src/eth_cheat_codes.ts +314 -0
- package/src/index.ts +13 -0
- package/src/l1_contract_addresses.ts +109 -0
- package/src/l1_reader.ts +42 -0
- package/src/l1_tx_utils.ts +847 -0
- package/src/l1_tx_utils_with_blobs.ts +86 -0
- package/src/queries.ts +58 -0
- package/src/test/delayed_tx_utils.ts +24 -0
- package/src/test/eth_cheat_codes_with_state.ts +38 -0
- package/src/test/index.ts +5 -0
- package/src/test/start_anvil.ts +52 -0
- package/src/test/tx_delayer.ts +163 -0
- package/src/test/upgrade_utils.ts +100 -0
- package/src/types.ts +33 -0
- package/src/utils.ts +276 -0
package/dest/utils.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { ErrorsAbi } from '@aztec/l1-artifacts';
|
|
2
|
+
import { BaseError, ContractFunctionRevertedError, decodeErrorResult, decodeEventLog } from 'viem';
|
|
3
|
+
export class FormattedViemError extends Error {
|
|
4
|
+
metaMessages;
|
|
5
|
+
constructor(message, metaMessages){
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'FormattedViemError';
|
|
8
|
+
this.metaMessages = metaMessages;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function extractEvent(logs, address, abi, eventName, filter, logger) {
|
|
12
|
+
const event = tryExtractEvent(logs, address, abi, eventName, filter, logger);
|
|
13
|
+
if (!event) {
|
|
14
|
+
throw new Error(`Failed to find matching event ${eventName} for contract ${address}`);
|
|
15
|
+
}
|
|
16
|
+
return event;
|
|
17
|
+
}
|
|
18
|
+
function tryExtractEvent(logs, address, abi, eventName, filter, logger) {
|
|
19
|
+
for (const log of logs){
|
|
20
|
+
if (log.address.toLowerCase() === address.toLowerCase()) {
|
|
21
|
+
try {
|
|
22
|
+
const decodedEvent = decodeEventLog({
|
|
23
|
+
abi,
|
|
24
|
+
...log
|
|
25
|
+
});
|
|
26
|
+
if (decodedEvent.eventName === eventName) {
|
|
27
|
+
const matchingEvent = decodedEvent;
|
|
28
|
+
if (!filter || filter(matchingEvent)) {
|
|
29
|
+
return matchingEvent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
logger?.warn(`Failed to decode event log for contract ${address}: ${err}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function prettyLogViemErrorMsg(err) {
|
|
39
|
+
if (err instanceof BaseError) {
|
|
40
|
+
const revertError = err.walk((err)=>err instanceof ContractFunctionRevertedError);
|
|
41
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
42
|
+
const errorName = revertError.data?.errorName ?? '';
|
|
43
|
+
const args = revertError.metaMessages && revertError.metaMessages?.length > 1 ? revertError.metaMessages[1].trimStart() : '';
|
|
44
|
+
return `${errorName}${args}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return err?.message ?? err;
|
|
48
|
+
}
|
|
49
|
+
function getNestedErrorData(error) {
|
|
50
|
+
// If nothing, bail
|
|
51
|
+
if (!error) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
// If it's an object with a `data` property, return it
|
|
55
|
+
// (Remember to check TS type-safely or cast as needed)
|
|
56
|
+
if (typeof error === 'object' && error !== null && 'data' in error) {
|
|
57
|
+
const possibleData = error.data;
|
|
58
|
+
if (typeof possibleData === 'string' && possibleData.startsWith('0x')) {
|
|
59
|
+
return possibleData;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// If it has a `cause`, recurse
|
|
63
|
+
if (typeof error === 'object' && error !== null && 'cause' in error) {
|
|
64
|
+
return getNestedErrorData(error.cause);
|
|
65
|
+
}
|
|
66
|
+
// Not found
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Formats a Viem error into a FormattedViemError instance.
|
|
71
|
+
* @param error - The error to format.
|
|
72
|
+
* @param abi - The ABI to use for decoding.
|
|
73
|
+
* @returns A FormattedViemError instance.
|
|
74
|
+
*/ export function formatViemError(error, abi = ErrorsAbi) {
|
|
75
|
+
// If error is already a FormattedViemError, return it as is
|
|
76
|
+
if (error instanceof FormattedViemError) {
|
|
77
|
+
return error;
|
|
78
|
+
}
|
|
79
|
+
// First try to decode as a custom error using the ABI
|
|
80
|
+
try {
|
|
81
|
+
const data = getNestedErrorData(error);
|
|
82
|
+
if (data) {
|
|
83
|
+
// Try to decode the error data using the ABI
|
|
84
|
+
const decoded = decodeErrorResult({
|
|
85
|
+
abi,
|
|
86
|
+
data: data
|
|
87
|
+
});
|
|
88
|
+
if (decoded) {
|
|
89
|
+
return new FormattedViemError(`${decoded.errorName}(${decoded.args?.join(', ') ?? ''})`, error?.metaMessages);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// If it's a BaseError, try to get the custom error through ContractFunctionRevertedError
|
|
93
|
+
if (error instanceof BaseError) {
|
|
94
|
+
const revertError = error.walk((err)=>err instanceof ContractFunctionRevertedError);
|
|
95
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
96
|
+
let errorName = revertError.data?.errorName;
|
|
97
|
+
if (!errorName) {
|
|
98
|
+
errorName = revertError.signature ?? '';
|
|
99
|
+
}
|
|
100
|
+
const args = revertError.metaMessages && revertError.metaMessages?.length > 1 ? revertError.metaMessages[1].trimStart() : '';
|
|
101
|
+
return new FormattedViemError(`${errorName}${args}`, error?.metaMessages);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (decodeErr) {
|
|
105
|
+
// If decoding fails, we fall back to the original formatting
|
|
106
|
+
}
|
|
107
|
+
// If it's a regular Error instance, return it with its message
|
|
108
|
+
if (error instanceof Error) {
|
|
109
|
+
return error;
|
|
110
|
+
}
|
|
111
|
+
// Original formatting logic for non-custom errors
|
|
112
|
+
const truncateHex = (hex, length = 100)=>{
|
|
113
|
+
if (!hex || typeof hex !== 'string') {
|
|
114
|
+
return hex;
|
|
115
|
+
}
|
|
116
|
+
if (!hex.startsWith('0x')) {
|
|
117
|
+
return hex;
|
|
118
|
+
}
|
|
119
|
+
if (hex.length <= length * 2) {
|
|
120
|
+
return hex;
|
|
121
|
+
}
|
|
122
|
+
return `${hex.slice(0, length)}...${hex.slice(-length)}`;
|
|
123
|
+
};
|
|
124
|
+
const formatRequestBody = (body)=>{
|
|
125
|
+
try {
|
|
126
|
+
const parsed = JSON.parse(body);
|
|
127
|
+
// Recursively process all parameters that might contain hex strings
|
|
128
|
+
const processParams = (obj)=>{
|
|
129
|
+
if (Array.isArray(obj)) {
|
|
130
|
+
return obj.map((item)=>processParams(item));
|
|
131
|
+
}
|
|
132
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const [key, value] of Object.entries(obj)){
|
|
135
|
+
result[key] = processParams(value);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
if (typeof obj === 'string') {
|
|
140
|
+
if (obj.startsWith('0x')) {
|
|
141
|
+
return truncateHex(obj);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return obj;
|
|
145
|
+
};
|
|
146
|
+
// Process the entire request body
|
|
147
|
+
const processed = processParams(parsed);
|
|
148
|
+
return JSON.stringify(processed, null, 2);
|
|
149
|
+
} catch {
|
|
150
|
+
return body;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const truncateHexStringsInText = (text)=>{
|
|
154
|
+
const hexRegex = /\b0x[a-fA-F0-9]{10,}/g;
|
|
155
|
+
return text.replace(hexRegex, (hex)=>truncateHex(hex));
|
|
156
|
+
};
|
|
157
|
+
const extractAndFormatRequestBody = (message)=>{
|
|
158
|
+
// First handle Request body JSON
|
|
159
|
+
const requestBodyRegex = /Request body: ({[\s\S]*?})\n/g;
|
|
160
|
+
let result = message.replace(requestBodyRegex, (match, body)=>{
|
|
161
|
+
return `Request body: ${formatRequestBody(body)}\n`;
|
|
162
|
+
});
|
|
163
|
+
// Then handle Arguments section
|
|
164
|
+
const argsRegex = /((?:Request |Estimate Gas )?Arguments:[\s\S]*?(?=\n\n|$))/g;
|
|
165
|
+
result = result.replace(argsRegex, (section)=>{
|
|
166
|
+
const lines = section.split('\n');
|
|
167
|
+
const processedLines = lines.map((line)=>{
|
|
168
|
+
// Check if line contains a colon followed by content
|
|
169
|
+
const colonIndex = line.indexOf(':');
|
|
170
|
+
if (colonIndex !== -1) {
|
|
171
|
+
const [prefix, content] = [
|
|
172
|
+
line.slice(0, colonIndex + 1),
|
|
173
|
+
line.slice(colonIndex + 1).trim()
|
|
174
|
+
];
|
|
175
|
+
// If content contains a hex string, truncate it
|
|
176
|
+
if (content.includes('0x')) {
|
|
177
|
+
const hexMatches = content.match(/0x[a-fA-F0-9]+/g) || [];
|
|
178
|
+
let processedContent = content;
|
|
179
|
+
hexMatches.forEach((hex)=>{
|
|
180
|
+
processedContent = processedContent.replace(hex, truncateHex(hex));
|
|
181
|
+
});
|
|
182
|
+
return `${prefix} ${processedContent}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return line;
|
|
186
|
+
});
|
|
187
|
+
return processedLines.join('\n');
|
|
188
|
+
});
|
|
189
|
+
// Finally, catch any remaining hex strings in the message
|
|
190
|
+
result = truncateHexStringsInText(result);
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
const formattedRes = extractAndFormatRequestBody(error?.message || String(error));
|
|
194
|
+
return new FormattedViemError(formattedRes.replace(/\\n/g, '\n'), error?.metaMessages);
|
|
195
|
+
}
|
|
196
|
+
export function tryGetCustomErrorName(err) {
|
|
197
|
+
try {
|
|
198
|
+
// See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
|
|
199
|
+
if (err.name === 'ViemError' || err.name === 'ContractFunctionExecutionError') {
|
|
200
|
+
const baseError = err;
|
|
201
|
+
const revertError = baseError.walk((err)=>err.name === 'ContractFunctionRevertedError');
|
|
202
|
+
if (revertError) {
|
|
203
|
+
return revertError.data?.errorName;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (_e) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aztec/ethereum",
|
|
3
|
+
"version": "0.0.0-test.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dest/index.js",
|
|
7
|
+
"./eth-cheatcodes": "./dest/eth_cheat_codes.js",
|
|
8
|
+
"./test": "./dest/test/index.js",
|
|
9
|
+
"./contracts": "./dest/contracts/index.js",
|
|
10
|
+
"./deploy-l1-contracts": "./dest/deploy_l1_contracts.js",
|
|
11
|
+
"./l1-contract-addresses": "./dest/l1_contract_addresses.js",
|
|
12
|
+
"./l1-tx-utils-with-blobs": "./dest/l1_tx_utils_with_blobs.js",
|
|
13
|
+
"./utils": "./dest/utils.js"
|
|
14
|
+
},
|
|
15
|
+
"typedocOptions": {
|
|
16
|
+
"entryPoints": [
|
|
17
|
+
"./src/index.ts"
|
|
18
|
+
],
|
|
19
|
+
"name": "Ethereum",
|
|
20
|
+
"tsconfig": "./tsconfig.json"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "yarn clean && tsc -b",
|
|
24
|
+
"build:dev": "tsc -b --watch",
|
|
25
|
+
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
26
|
+
"formatting": "run -T prettier --check ./src && run -T eslint ./src",
|
|
27
|
+
"formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
|
|
28
|
+
"start:dev": "tsc-watch -p tsconfig.json --onSuccess 'yarn start'",
|
|
29
|
+
"start": "node ./dest/index.js",
|
|
30
|
+
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
31
|
+
},
|
|
32
|
+
"inherits": [
|
|
33
|
+
"../package.common.json"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@aztec/blob-lib": "0.0.0-test.0",
|
|
37
|
+
"@aztec/foundation": "0.0.0-test.0",
|
|
38
|
+
"@aztec/l1-artifacts": "0.0.0-test.0",
|
|
39
|
+
"@viem/anvil": "^0.0.10",
|
|
40
|
+
"dotenv": "^16.0.3",
|
|
41
|
+
"tslib": "^2.4.0",
|
|
42
|
+
"viem": "2.22.8",
|
|
43
|
+
"zod": "^3.23.8"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@jest/globals": "^29.5.0",
|
|
47
|
+
"@types/jest": "^29.5.0",
|
|
48
|
+
"@types/node": "^18.14.6",
|
|
49
|
+
"@viem/anvil": "^0.0.10",
|
|
50
|
+
"get-port": "^7.1.0",
|
|
51
|
+
"jest": "^29.5.0",
|
|
52
|
+
"lodash.omit": "^4.5.0",
|
|
53
|
+
"ts-node": "^10.9.1",
|
|
54
|
+
"typescript": "^5.0.4"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dest",
|
|
58
|
+
"src",
|
|
59
|
+
"!*.test.*"
|
|
60
|
+
],
|
|
61
|
+
"types": "./dest/index.d.ts",
|
|
62
|
+
"jest": {
|
|
63
|
+
"moduleNameMapper": {
|
|
64
|
+
"^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
|
|
65
|
+
},
|
|
66
|
+
"testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
|
|
67
|
+
"rootDir": "./src",
|
|
68
|
+
"transform": {
|
|
69
|
+
"^.+\\.tsx?$": [
|
|
70
|
+
"@swc/jest",
|
|
71
|
+
{
|
|
72
|
+
"jsc": {
|
|
73
|
+
"parser": {
|
|
74
|
+
"syntax": "typescript",
|
|
75
|
+
"decorators": true
|
|
76
|
+
},
|
|
77
|
+
"transform": {
|
|
78
|
+
"decoratorVersion": "2022-03"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"extensionsToTreatAsEsm": [
|
|
85
|
+
".ts"
|
|
86
|
+
],
|
|
87
|
+
"reporters": [
|
|
88
|
+
"default"
|
|
89
|
+
],
|
|
90
|
+
"testTimeout": 120000,
|
|
91
|
+
"setupFiles": [
|
|
92
|
+
"../../foundation/src/jest/setup.mjs"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"engines": {
|
|
96
|
+
"node": ">=18"
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/chain.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Chain } from 'viem';
|
|
2
|
+
import { foundry } from 'viem/chains';
|
|
3
|
+
|
|
4
|
+
import { AZTEC_TEST_CHAIN_ID } from './constants.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interface containing the connection and chain properties to interact with a blockchain.
|
|
8
|
+
*/
|
|
9
|
+
export interface EthereumChain {
|
|
10
|
+
/**
|
|
11
|
+
* An instance of the viem chain data.
|
|
12
|
+
*/
|
|
13
|
+
chainInfo: Chain;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The list of actual urls to be used.
|
|
17
|
+
*/
|
|
18
|
+
rpcUrls: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Helper function to create an instance of Aztec Chain from an rpc url and chain id.
|
|
23
|
+
* @param rpcUrls - The rpc url of the chain or a chain identifier (e.g. 'http://localhost:8545')
|
|
24
|
+
* @param chainId - The chain id of the chain or a chain identifier (e.g. 1337)
|
|
25
|
+
*/
|
|
26
|
+
export function createEthereumChain(rpcUrls: string[], _chainId: number | string): EthereumChain {
|
|
27
|
+
let chainId: number;
|
|
28
|
+
if (typeof _chainId === 'string') {
|
|
29
|
+
chainId = +_chainId;
|
|
30
|
+
} else {
|
|
31
|
+
chainId = _chainId;
|
|
32
|
+
}
|
|
33
|
+
if (chainId) {
|
|
34
|
+
return {
|
|
35
|
+
chainInfo: {
|
|
36
|
+
id: chainId,
|
|
37
|
+
name: 'Ethereum',
|
|
38
|
+
rpcUrls: {
|
|
39
|
+
default: {
|
|
40
|
+
http: rpcUrls,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
nativeCurrency: {
|
|
44
|
+
decimals: 18,
|
|
45
|
+
name: 'Ether',
|
|
46
|
+
symbol: 'ETH',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
rpcUrls,
|
|
50
|
+
};
|
|
51
|
+
} else {
|
|
52
|
+
return {
|
|
53
|
+
chainInfo: foundry,
|
|
54
|
+
rpcUrls,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Helper function to determine if a chain id is an instance of Anvil
|
|
61
|
+
*/
|
|
62
|
+
export function isAnvilTestChain(_chainId: number | string): boolean {
|
|
63
|
+
let chainId: number;
|
|
64
|
+
if (typeof _chainId === 'string') {
|
|
65
|
+
chainId = +_chainId;
|
|
66
|
+
} else {
|
|
67
|
+
chainId = _chainId;
|
|
68
|
+
}
|
|
69
|
+
const testChains = [foundry.id, AZTEC_TEST_CHAIN_ID];
|
|
70
|
+
return testChains.includes(chainId);
|
|
71
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
2
|
+
import { retryUntil } from '@aztec/foundation/retry';
|
|
3
|
+
|
|
4
|
+
import { createPublicClient, fallback, http } from 'viem';
|
|
5
|
+
|
|
6
|
+
import { createEthereumChain } from './chain.js';
|
|
7
|
+
import type { ViemPublicClient } from './types.js';
|
|
8
|
+
|
|
9
|
+
type Config = {
|
|
10
|
+
/** The RPC Url of the ethereum host. */
|
|
11
|
+
l1RpcUrls: string[];
|
|
12
|
+
/** The chain ID of the ethereum host. */
|
|
13
|
+
l1ChainId: number;
|
|
14
|
+
/** The polling interval viem uses in ms */
|
|
15
|
+
viemPollingIntervalMS?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// TODO: Use these methods to abstract the creation of viem clients.
|
|
19
|
+
|
|
20
|
+
/** Returns a viem public client given the L1 config. */
|
|
21
|
+
export function getPublicClient(config: Config): ViemPublicClient {
|
|
22
|
+
const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
23
|
+
return createPublicClient({
|
|
24
|
+
chain: chain.chainInfo,
|
|
25
|
+
transport: fallback(config.l1RpcUrls.map(url => http(url))),
|
|
26
|
+
pollingInterval: config.viemPollingIntervalMS,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Returns a viem public client after waiting for the L1 RPC node to become available. */
|
|
31
|
+
export async function waitForPublicClient(config: Config, logger?: Logger): Promise<ViemPublicClient> {
|
|
32
|
+
const client = getPublicClient(config);
|
|
33
|
+
await waitForRpc(client, config, logger);
|
|
34
|
+
return client;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function waitForRpc(client: ViemPublicClient, config: Config, logger?: Logger) {
|
|
38
|
+
const l1ChainId = await retryUntil(
|
|
39
|
+
async () => {
|
|
40
|
+
let chainId = 0;
|
|
41
|
+
try {
|
|
42
|
+
chainId = await client.getChainId();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
logger?.warn(`Failed to connect to Ethereum node at ${config.l1RpcUrls.join(', ')}. Retrying...`);
|
|
45
|
+
}
|
|
46
|
+
return chainId;
|
|
47
|
+
},
|
|
48
|
+
`L1 RPC url at ${config.l1RpcUrls.join(', ')}`,
|
|
49
|
+
600,
|
|
50
|
+
1,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (l1ChainId !== config.l1ChainId) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Ethereum node at ${config.l1RpcUrls.join(', ')} has chain ID ${l1ChainId} but expected ${config.l1ChainId}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConfigMappingsType,
|
|
3
|
+
bigintConfigHelper,
|
|
4
|
+
getConfigFromMappings,
|
|
5
|
+
numberConfigHelper,
|
|
6
|
+
} from '@aztec/foundation/config';
|
|
7
|
+
|
|
8
|
+
import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from './l1_tx_utils.js';
|
|
9
|
+
|
|
10
|
+
export type L1ContractsConfig = {
|
|
11
|
+
/** How many seconds an L1 slot lasts. */
|
|
12
|
+
ethereumSlotDuration: number;
|
|
13
|
+
/** How many seconds an L2 slots lasts (must be multiple of ethereum slot duration). */
|
|
14
|
+
aztecSlotDuration: number;
|
|
15
|
+
/** How many L2 slots an epoch lasts. */
|
|
16
|
+
aztecEpochDuration: number;
|
|
17
|
+
/** The target validator committee size. */
|
|
18
|
+
aztecTargetCommitteeSize: number;
|
|
19
|
+
/** The number of L2 slots that we can wait for a proof of an epoch to be produced. */
|
|
20
|
+
aztecProofSubmissionWindow: number;
|
|
21
|
+
/** The minimum stake for a validator. */
|
|
22
|
+
minimumStake: bigint;
|
|
23
|
+
/** The slashing quorum */
|
|
24
|
+
slashingQuorum: number;
|
|
25
|
+
/** The slashing round size */
|
|
26
|
+
slashingRoundSize: number;
|
|
27
|
+
/** Governance proposing quorum */
|
|
28
|
+
governanceProposerQuorum: number;
|
|
29
|
+
/** Governance proposing round size */
|
|
30
|
+
governanceProposerRoundSize: number;
|
|
31
|
+
} & L1TxUtilsConfig;
|
|
32
|
+
|
|
33
|
+
export const DefaultL1ContractsConfig = {
|
|
34
|
+
ethereumSlotDuration: 12,
|
|
35
|
+
aztecSlotDuration: 24,
|
|
36
|
+
aztecEpochDuration: 16,
|
|
37
|
+
aztecTargetCommitteeSize: 48,
|
|
38
|
+
aztecProofSubmissionWindow: 31, // you have a full epoch to submit a proof after the epoch to prove ends
|
|
39
|
+
minimumStake: BigInt(100e18),
|
|
40
|
+
slashingQuorum: 6,
|
|
41
|
+
slashingRoundSize: 10,
|
|
42
|
+
governanceProposerQuorum: 6,
|
|
43
|
+
governanceProposerRoundSize: 10,
|
|
44
|
+
} satisfies L1ContractsConfig;
|
|
45
|
+
|
|
46
|
+
export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> = {
|
|
47
|
+
ethereumSlotDuration: {
|
|
48
|
+
env: 'ETHEREUM_SLOT_DURATION',
|
|
49
|
+
description: 'How many seconds an L1 slot lasts.',
|
|
50
|
+
...numberConfigHelper(DefaultL1ContractsConfig.ethereumSlotDuration),
|
|
51
|
+
},
|
|
52
|
+
aztecSlotDuration: {
|
|
53
|
+
env: 'AZTEC_SLOT_DURATION',
|
|
54
|
+
description: 'How many seconds an L2 slots lasts (must be multiple of ethereum slot duration).',
|
|
55
|
+
...numberConfigHelper(DefaultL1ContractsConfig.aztecSlotDuration),
|
|
56
|
+
},
|
|
57
|
+
aztecEpochDuration: {
|
|
58
|
+
env: 'AZTEC_EPOCH_DURATION',
|
|
59
|
+
description: `How many L2 slots an epoch lasts (maximum AZTEC_MAX_EPOCH_DURATION).`,
|
|
60
|
+
...numberConfigHelper(DefaultL1ContractsConfig.aztecEpochDuration),
|
|
61
|
+
},
|
|
62
|
+
aztecTargetCommitteeSize: {
|
|
63
|
+
env: 'AZTEC_TARGET_COMMITTEE_SIZE',
|
|
64
|
+
description: 'The target validator committee size.',
|
|
65
|
+
...numberConfigHelper(DefaultL1ContractsConfig.aztecTargetCommitteeSize),
|
|
66
|
+
},
|
|
67
|
+
aztecProofSubmissionWindow: {
|
|
68
|
+
env: 'AZTEC_PROOF_SUBMISSION_WINDOW',
|
|
69
|
+
description:
|
|
70
|
+
'The number of L2 slots that a proof for an epoch can be submitted in, starting from the beginning of the epoch.',
|
|
71
|
+
...numberConfigHelper(DefaultL1ContractsConfig.aztecProofSubmissionWindow),
|
|
72
|
+
},
|
|
73
|
+
minimumStake: {
|
|
74
|
+
env: 'AZTEC_MINIMUM_STAKE',
|
|
75
|
+
description: 'The minimum stake for a validator.',
|
|
76
|
+
...bigintConfigHelper(DefaultL1ContractsConfig.minimumStake),
|
|
77
|
+
},
|
|
78
|
+
slashingQuorum: {
|
|
79
|
+
env: 'AZTEC_SLASHING_QUORUM',
|
|
80
|
+
description: 'The slashing quorum',
|
|
81
|
+
...numberConfigHelper(DefaultL1ContractsConfig.slashingQuorum),
|
|
82
|
+
},
|
|
83
|
+
slashingRoundSize: {
|
|
84
|
+
env: 'AZTEC_SLASHING_ROUND_SIZE',
|
|
85
|
+
description: 'The slashing round size',
|
|
86
|
+
...numberConfigHelper(DefaultL1ContractsConfig.slashingRoundSize),
|
|
87
|
+
},
|
|
88
|
+
governanceProposerQuorum: {
|
|
89
|
+
env: 'AZTEC_GOVERNANCE_PROPOSER_QUORUM',
|
|
90
|
+
description: 'The governance proposing quorum',
|
|
91
|
+
...numberConfigHelper(DefaultL1ContractsConfig.governanceProposerQuorum),
|
|
92
|
+
},
|
|
93
|
+
governanceProposerRoundSize: {
|
|
94
|
+
env: 'AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE',
|
|
95
|
+
description: 'The governance proposing round size',
|
|
96
|
+
...numberConfigHelper(DefaultL1ContractsConfig.governanceProposerRoundSize),
|
|
97
|
+
},
|
|
98
|
+
...l1TxUtilsConfigMappings,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export function getL1ContractsConfigEnvVars(): L1ContractsConfig {
|
|
102
|
+
return getConfigFromMappings(l1ContractsConfigMappings);
|
|
103
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EmpireBaseAbi } from '@aztec/l1-artifacts/EmpireBaseAbi';
|
|
2
|
+
|
|
3
|
+
import { type Hex, encodeFunctionData } from 'viem';
|
|
4
|
+
|
|
5
|
+
import type { L1TxRequest } from '../l1_tx_utils.js';
|
|
6
|
+
|
|
7
|
+
export interface IEmpireBase {
|
|
8
|
+
getRoundInfo(rollupAddress: Hex, round: bigint): Promise<{ lastVote: bigint; leader: Hex; executed: boolean }>;
|
|
9
|
+
computeRound(slot: bigint): Promise<bigint>;
|
|
10
|
+
createVoteRequest(payload: Hex): L1TxRequest;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function encodeVote(payload: Hex): Hex {
|
|
14
|
+
return encodeFunctionData({
|
|
15
|
+
abi: EmpireBaseAbi,
|
|
16
|
+
functionName: 'vote',
|
|
17
|
+
args: [payload],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
|
+
import { TestERC20Abi as FeeJuiceAbi } from '@aztec/l1-artifacts';
|
|
3
|
+
|
|
4
|
+
import { type GetContractReturnType, type Hex, getContract } from 'viem';
|
|
5
|
+
|
|
6
|
+
import type { L1Clients } from '../types.js';
|
|
7
|
+
|
|
8
|
+
export class FeeJuiceContract {
|
|
9
|
+
private readonly publicFeeJuice: GetContractReturnType<typeof FeeJuiceAbi, L1Clients['publicClient']>;
|
|
10
|
+
private readonly walletFeeJuice: GetContractReturnType<typeof FeeJuiceAbi, L1Clients['walletClient']> | undefined;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
address: Hex,
|
|
14
|
+
public readonly publicClient: L1Clients['publicClient'],
|
|
15
|
+
public readonly walletClient: L1Clients['walletClient'] | undefined,
|
|
16
|
+
) {
|
|
17
|
+
this.publicFeeJuice = getContract({ address, abi: FeeJuiceAbi, client: publicClient });
|
|
18
|
+
this.walletFeeJuice = walletClient ? getContract({ address, abi: FeeJuiceAbi, client: walletClient }) : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public get address() {
|
|
22
|
+
return EthAddress.fromString(this.publicFeeJuice.address);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private assertWalletFeeJuice() {
|
|
26
|
+
if (!this.walletFeeJuice) {
|
|
27
|
+
throw new Error('Wallet client is required for this operation');
|
|
28
|
+
}
|
|
29
|
+
return this.walletFeeJuice;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public async mint(to: Hex, amount: bigint) {
|
|
33
|
+
const walletFeeJuice = this.assertWalletFeeJuice();
|
|
34
|
+
const tx = await walletFeeJuice.write.mint([to, amount]);
|
|
35
|
+
await this.publicClient.waitForTransactionReceipt({ hash: tx });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async approve(spender: Hex, amount: bigint) {
|
|
39
|
+
const walletFeeJuice = this.assertWalletFeeJuice();
|
|
40
|
+
const tx = await walletFeeJuice.write.approve([spender, amount]);
|
|
41
|
+
await this.publicClient.waitForTransactionReceipt({ hash: tx });
|
|
42
|
+
}
|
|
43
|
+
}
|