import { EOL } from 'node:os'; import { Transform } from 'node:stream'; import { getAddress } from 'viem'; /** * This script is used to deploy contracts using Forge. * It outputs a solidity script that can be used to deploy contracts and execute transactions. * Note: Make sure you add `.sol` extension to the output file. */ const footer = ` } } `; export const createRenderer = (blockNumber: number) => new Transform({ objectMode: true, construct(cb) { const header = ` // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; /** * @notice The following script is automatically generated by https://usecannon.com */ contract CannonDeploy is Script { uint256 public deployBlockNumber = ${blockNumber}; mapping(bytes32 => address) public addresses; mapping(bytes32 => string) public settings; function getAddress(string memory label) external returns (address) { return addresses[keccak256(bytes(label))]; } function getSetting(string memory label) external returns (string memory) { return settings[keccak256(bytes(label))]; } function run() external { bytes memory data; `; this.push(header); // Bash script header return cb(); }, transform(line, _, cb) { const title = `[${line.type}.${line.label}]`; const indent = ' '.repeat(2); if (!line.result) { // Logging non-transaction steps this.push(`${indent}// no on-chain transactions required\n`); } else { // Loggin txn types for (const c in line.result.contracts) { const fullContractPath = [...line.cloneNames, c].join('.'); this.push(`${indent}// > CONTRACT DEPLOYED: ${line.result.contracts[c].address}\n`); this.push( `${indent}addresses[keccak256("${fullContractPath}")] = address(${getAddress( line.result.contracts[c].address )});\n` ); this.push(`${indent}vm.label(${getAddress(line.result.contracts[c].address)}, "${fullContractPath}");\n`); } for (const t in line.result.txns) { this.push(`${indent}// > TRANSACTION EXECUTED: ${Object.keys(line.result.txns[t].events).join(', ')}\n`); } for (const s in line.result.settings) { this.push( `${indent}settings[keccak256("${[...line.cloneNames, s].join('.')}")] = "${line.result.settings[s]}";\n` ); } } for (const { to, from, input, value } of line.txns) { if (from) { this.push(`${indent}vm.broadcast(${getAddress(from)});\n`); this.push(`${indent}data = hex"${input.slice(2)}";\n`); } this.push('assembly {\n'); if (!to) { this.push(`${indent} pop(create(0, add(data, 0x20), ${input.length / 2 - 1}))\n`); } else { this.push(`${indent} pop(call(gas(), ${to}, ${value}, add(data, 0x20), ${input.length / 2 - 1}, 0, 0))\n`); } this.push('}\n'); // Log the title and command //const indent = ' '.repeat(line.depth); //this.push(`${indent}echo "Executing: ${title}"${EOL}`); //this.push(`${indent}${command}${EOL}${EOL}`); } this.push(`${indent}// ${line.phase === 'pre' ? 'START' : 'END'} ${title}${EOL}`); if (line.phase === 'post') { this.push(EOL); } return cb(); }, flush(cb) { this.push(footer); // Bash script footer return cb(); }, });