Skip to content

Commit da90983

Browse files
committed
feat: Add cannon, registering new sqd-command and adding codegen for all cannon available testnet deployments.
1 parent 870a914 commit da90983

5 files changed

Lines changed: 2434 additions & 88 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ npm-debug.log*
2929
# Generated folders/files
3030
/src/model
3131
/src/abi
32+
/src/deployments
3233

3334
#Test outputs
3435
/coverage

codegen.mjs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import { readdir, readFile, writeFile } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
import shell from 'shelljs';
4+
import {
5+
arbitrum,
6+
arbitrumSepolia,
7+
base,
8+
baseSepolia,
9+
cannon,
10+
mainnet,
11+
optimism,
12+
optimismSepolia,
13+
sepolia,
14+
} from 'viem/chains';
15+
16+
//TODO: Not available yet on cannon.
17+
const supportedMainnets = [arbitrum.id, base.id, mainnet.id, optimism.id];
18+
const supportedTestnets = [
19+
arbitrumSepolia.id,
20+
baseSepolia.id,
21+
cannon.id,
22+
optimismSepolia.id,
23+
sepolia.id,
24+
];
25+
//TODO: evaluate to be a env-var. But it needs to sync with npm-deps.
26+
const packageRef = 'cartesi-rollups:2.0.0-rc.17';
27+
const abiFolder = join(process.cwd(), 'abi');
28+
const outFolder = join(process.cwd(), 'src', 'deployments');
29+
const codegenMetaFileName = '__meta.json';
30+
const v2OutDir = join(
31+
process.cwd(),
32+
'node_modules',
33+
'@cartesi',
34+
'rollups-v2',
35+
'out',
36+
);
37+
38+
const buildContractsOfInterest = (chainId) => [
39+
{
40+
name: 'CartesiApplication',
41+
targetFile: join(v2OutDir, 'Application.sol', 'Application.json'),
42+
},
43+
{
44+
name: 'CartesiApplicationFactory',
45+
targetFile: join(outFolder, chainId, 'ApplicationFactory.json'),
46+
},
47+
{
48+
name: 'InputBoxV2',
49+
targetFile: join(outFolder, chainId, 'InputBox.json'),
50+
},
51+
];
52+
53+
const shouldSkipInspect = async ({ packageRef, targetFolder }) => {
54+
const meta = await readFile(join(targetFolder, codegenMetaFileName), {
55+
encoding: 'utf-8',
56+
})
57+
.then((fileContent) => {
58+
try {
59+
return JSON.parse(fileContent);
60+
} catch (error) {
61+
console.error(error);
62+
}
63+
return null;
64+
})
65+
.catch((_) => {
66+
return null;
67+
});
68+
69+
const isPackageRefMatching = meta?.packageRef === packageRef;
70+
71+
if (isPackageRefMatching)
72+
console.log(`Skipping Cannon inspect for package ${packageRef}`);
73+
74+
return isPackageRefMatching;
75+
};
76+
77+
const generateContractsJSON = async (sourceFolder, targetFolder) => {
78+
const files = await readdir(sourceFolder);
79+
const contracts = {};
80+
81+
for (const file of files) {
82+
if (file === codegenMetaFileName) continue;
83+
84+
const filePath = join(sourceFolder, file);
85+
const [name] = file.split('.');
86+
const content = await readFile(filePath);
87+
contracts[name] = JSON.parse(content);
88+
}
89+
90+
await writeFile(
91+
join(targetFolder, 'contracts.json'),
92+
JSON.stringify({ contracts }, null, ' '),
93+
{ encoding: 'utf-8' },
94+
);
95+
};
96+
97+
const execCannonInspect = async ({ packageRef, chainId, outFolder }) => {
98+
const targetFolder = join(outFolder, chainId);
99+
100+
const shouldSkip = await shouldSkipInspect({ packageRef, targetFolder });
101+
102+
if (shouldSkip) return { ok: true };
103+
104+
const child = shell.exec(
105+
`npm run cannon -- inspect ${packageRef} --chain-id ${chainId} --write-deployments ${targetFolder} --quiet`,
106+
{ async: true },
107+
);
108+
109+
const outputPromise = new Promise((resolve) => {
110+
child.on('exit', (code) => {
111+
resolve({ code });
112+
});
113+
});
114+
115+
const output = await outputPromise;
116+
117+
const isSuccess = output.code === 0;
118+
119+
if (isSuccess) {
120+
await Promise.all([
121+
generateContractsJSON(targetFolder, targetFolder),
122+
writeFile(
123+
join(targetFolder, codegenMetaFileName),
124+
JSON.stringify({
125+
packageRef,
126+
timestamp: Math.floor(Date.now() / 1000),
127+
desc: 'Autogenerated. Do not change it manually.',
128+
}),
129+
{ encoding: 'utf-8' },
130+
),
131+
]);
132+
}
133+
134+
return { ok: isSuccess };
135+
};
136+
137+
const readABIFor = async (list) => {
138+
const contracts = await Promise.all(
139+
list.map((target) => {
140+
return readFile(target.targetFile, {
141+
encoding: 'utf-8',
142+
}).then((content) => {
143+
try {
144+
const config = JSON.parse(content);
145+
return { name: target.name, abi: config.abi };
146+
} catch (error) {
147+
console.error(error.message);
148+
return { name: target.name, abi: [] };
149+
}
150+
});
151+
}),
152+
);
153+
154+
return contracts;
155+
};
156+
157+
/**
158+
*
159+
* @param {string} chainId
160+
* @returns {void}
161+
*/
162+
async function codegen(chainId) {
163+
const { ok } = await execCannonInspect({
164+
packageRef,
165+
chainId,
166+
outFolder,
167+
});
168+
169+
if (!ok)
170+
return Promise.reject(
171+
`Failed during inspection of ${packageRef} for chain-id: ${chainId}`,
172+
);
173+
174+
return { ok: true, chainId };
175+
}
176+
177+
async function generateABIFiles(chainId) {
178+
const contracts = await readABIFor(
179+
buildContractsOfInterest(chainId.toString()),
180+
);
181+
182+
const result = await Promise.allSettled(
183+
contracts.map((contract) => {
184+
const content = JSON.stringify(contract.abi, null, ' ');
185+
const fileName = join(abiFolder, `${contract.name}.json`);
186+
return writeFile(fileName, content, { encoding: 'utf-8' });
187+
}),
188+
);
189+
190+
result.forEach((data, i) => {
191+
if (data.status === 'fulfilled')
192+
console.info(`${contracts[i].name} created at ${abiFolder}\n`);
193+
else
194+
console.error(
195+
`${contracts[i].name} failed to be created. Reason: ${data.reason}`,
196+
);
197+
});
198+
}
199+
200+
async function run() {
201+
const networks = [...supportedTestnets];
202+
const codegenPromises = networks.map((chainId) => {
203+
console.log(`Codegen for chain-id: ${chainId.toString()}`);
204+
return codegen(chainId.toString());
205+
});
206+
const codegenResult = await Promise.allSettled(codegenPromises);
207+
208+
const index = codegenResult.findIndex(
209+
(data) => data.status === 'fulfilled',
210+
);
211+
212+
if (index >= 0) await generateABIFiles(networks[index]);
213+
214+
codegenResult.forEach((data, i) => {
215+
if (data.status === 'fulfilled')
216+
console.info(
217+
`Rollups contracts for chain-id ${supportedTestnets[
218+
i
219+
].toString()} generated at ${outFolder}`,
220+
);
221+
else
222+
console.error(
223+
`Rollups contracts for chain-id ${supportedTestnets[
224+
i
225+
].toString()} failed to be created. Reason: ${data.reason}`,
226+
);
227+
});
228+
}
229+
230+
run();

commands.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
},
5050
"typegen": {
5151
"description": "Generate data access classes for an ABI file(s) in the ./abi folder",
52+
"deps": ["codegen:rollups"],
5253
"cmd": [
5354
"squid-evm-typegen",
5455
"./src/abi",
@@ -121,6 +122,10 @@
121122
"preload:apps": {
122123
"description": "Preload the address of applications created by CartesiDAppFactory",
123124
"cmd": ["npx", "--yes", "ts-node", "./preloaders/applicationLoader"]
125+
},
126+
"codegen:rollups": {
127+
"description": "Fetch cartesi/rollups deployment information and necessary ABIs to be used by squid-cli ops and application logic.",
128+
"cmd": ["node", "./codegen.mjs"]
124129
}
125130
}
126131
}

0 commit comments

Comments
 (0)