From b9efede6eb57d7b871a156df373e3f0c2e16e9b1 Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Wed, 10 Jun 2026 13:42:27 +0100 Subject: [PATCH 1/5] chore(devnet): Use test tokens from rollups-contracts artifacts. --- packages/devnet/script/Deploy.s.sol | 35 ----------------- packages/devnet/src/TestMultiToken.sol | 32 ---------------- packages/devnet/src/TestNFT.sol | 38 ------------------ packages/devnet/src/TestToken.sol | 53 -------------------------- 4 files changed, 158 deletions(-) delete mode 100644 packages/devnet/script/Deploy.s.sol delete mode 100644 packages/devnet/src/TestMultiToken.sol delete mode 100644 packages/devnet/src/TestNFT.sol delete mode 100644 packages/devnet/src/TestToken.sol diff --git a/packages/devnet/script/Deploy.s.sol b/packages/devnet/script/Deploy.s.sol deleted file mode 100644 index 1fc37e8a..00000000 --- a/packages/devnet/script/Deploy.s.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8; - -import {BaseDeploymentScript} from "./BaseDeploymentScript.sol"; -import {TestMultiToken} from "../src/TestMultiToken.sol"; -import {TestNFT} from "../src/TestNFT.sol"; -import {TestToken} from "../src/TestToken.sol"; - -contract Deploy is BaseDeploymentScript { - function run() public { - address deployer = msg.sender; - - vm.startBroadcast(); - - // Deploy TestMultiToken - _storeDeployment( - type(TestMultiToken).name, - _create2(type(TestMultiToken).creationCode, abi.encode(deployer)) - ); - - // Deploy TestNFT - _storeDeployment( - type(TestNFT).name, - _create2(type(TestNFT).creationCode, abi.encode(deployer)) - ); - - // Deploy TestToken - _storeDeployment( - type(TestToken).name, - _create2(type(TestToken).creationCode, abi.encode(deployer)) - ); - - vm.stopBroadcast(); - } -} diff --git a/packages/devnet/src/TestMultiToken.sol b/packages/devnet/src/TestMultiToken.sol deleted file mode 100644 index 0f30089d..00000000 --- a/packages/devnet/src/TestMultiToken.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Compatible with OpenZeppelin Contracts ^5.0 -pragma solidity ^0.8; - -import {ERC1155} from "@openzeppelin-contracts-5.5.0/token/ERC1155/ERC1155.sol"; -import {Ownable} from "@openzeppelin-contracts-5.5.0/access/Ownable.sol"; - -contract TestMultiToken is ERC1155, Ownable { - constructor(address initialOwner) ERC1155("") Ownable(initialOwner) {} - - function setURI(string memory newuri) public onlyOwner { - _setURI(newuri); - } - - function mint( - address account, - uint256 id, - uint256 amount, - bytes memory data - ) public onlyOwner { - _mint(account, id, amount, data); - } - - function mintBatch( - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public onlyOwner { - _mintBatch(to, ids, amounts, data); - } -} diff --git a/packages/devnet/src/TestNFT.sol b/packages/devnet/src/TestNFT.sol deleted file mode 100644 index 3c453a01..00000000 --- a/packages/devnet/src/TestNFT.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Compatible with OpenZeppelin Contracts ^5.0 -pragma solidity ^0.8; - -import {ERC721} from "@openzeppelin-contracts-5.5.0/token/ERC721/ERC721.sol"; -import { - ERC721URIStorage -} from "@openzeppelin-contracts-5.5.0/token/ERC721/extensions/ERC721URIStorage.sol"; -import {Ownable} from "@openzeppelin-contracts-5.5.0/access/Ownable.sol"; - -contract TestNFT is ERC721, ERC721URIStorage, Ownable { - constructor( - address initialOwner - ) ERC721("TestNFT", "SUNN") Ownable(initialOwner) {} - - function safeMint( - address to, - uint256 tokenId, - string memory uri - ) public onlyOwner { - _safeMint(to, tokenId); - _setTokenURI(tokenId, uri); - } - - // The following functions are overrides required by Solidity. - - function tokenURI( - uint256 tokenId - ) public view override(ERC721, ERC721URIStorage) returns (string memory) { - return super.tokenURI(tokenId); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(ERC721, ERC721URIStorage) returns (bool) { - return super.supportsInterface(interfaceId); - } -} diff --git a/packages/devnet/src/TestToken.sol b/packages/devnet/src/TestToken.sol deleted file mode 100644 index c71f5a11..00000000 --- a/packages/devnet/src/TestToken.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Compatible with OpenZeppelin Contracts ^5.0 -pragma solidity ^0.8; - -import {ERC20} from "@openzeppelin-contracts-5.5.0/token/ERC20/ERC20.sol"; -import { - ERC20Burnable -} from "@openzeppelin-contracts-5.5.0/token/ERC20/extensions/ERC20Burnable.sol"; -import { - ERC20Pausable -} from "@openzeppelin-contracts-5.5.0/token/ERC20/extensions/ERC20Pausable.sol"; -import { - AccessManaged -} from "@openzeppelin-contracts-5.5.0/access/manager/AccessManaged.sol"; -import { - ERC20Permit -} from "@openzeppelin-contracts-5.5.0/token/ERC20/extensions/ERC20Permit.sol"; - -contract TestToken is - ERC20, - ERC20Burnable, - ERC20Pausable, - AccessManaged, - ERC20Permit -{ - constructor( - address initialAuthority - ) - ERC20("TestToken", "TEST") - AccessManaged(initialAuthority) - ERC20Permit("TestToken") - { - _mint(initialAuthority, 1000000000 * 10 ** decimals()); - } - - function pause() public restricted { - _pause(); - } - - function unpause() public restricted { - _unpause(); - } - - // The following functions are overrides required by Solidity. - - function _update( - address from, - address to, - uint256 value - ) internal override(ERC20, ERC20Pausable) { - super._update(from, to, value); - } -} From 5fb6830ca88e235df21e97973e490e8f84e73596 Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Wed, 10 Jun 2026 13:44:50 +0100 Subject: [PATCH 2/5] feat(devnet): Add cartesi-rollups-contracts soldeer dependency. * Also downgrade @openzeppelin-contracts from 5.5.0 to 5.2.0 to align with version used by cartesi-rollups-contracts. --- packages/devnet/foundry.toml | 3 ++- packages/devnet/soldeer.lock | 15 +++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/devnet/foundry.toml b/packages/devnet/foundry.toml index 251fa960..6f091e00 100644 --- a/packages/devnet/foundry.toml +++ b/packages/devnet/foundry.toml @@ -6,5 +6,6 @@ optimizer = true fs_permissions = [{ access = "read-write", path = "build" }] [dependencies] -"@openzeppelin-contracts" = "5.5.0" +"@openzeppelin-contracts" = "5.2.0" forge-std = "1.9.6" +cartesi-rollups-contracts = "3.0.0-alpha.6" diff --git a/packages/devnet/soldeer.lock b/packages/devnet/soldeer.lock index 39167063..3c549c7a 100644 --- a/packages/devnet/soldeer.lock +++ b/packages/devnet/soldeer.lock @@ -1,9 +1,16 @@ [[dependencies]] name = "@openzeppelin-contracts" -version = "5.5.0" -url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/5_5_0_01-11-2025_09:56:40_contracts.zip" -checksum = "2ee78837130cf06b456d9ef7ab70753685d9c1c9c2c5b70f24bd4c16695d0337" -integrity = "da8336cf949f0e0667ae8360af849681e3a3e76d7e61e7a86b1a3414a158aeea" +version = "5.2.0" +url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/5_2_0_11-01-2025_09:30:20_contracts.zip" +checksum = "6dbd0440446b2ed16ca25e9f1af08fc0c5c1e73e71fee86ae8a00daa774e3817" +integrity = "4cb7f3777f67fdf4b7d0e2f94d2f93f198b2e5dce718b7062ac7c2c83e1183bd" + +[[dependencies]] +name = "cartesi-rollups-contracts" +version = "3.0.0-alpha.6" +url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_6_21-05-2026_12:17:44_rollups-contracts.zip" +checksum = "cdfd86f90895ba37e188103e8fb62d6ca8e45a53846ebc63cb7b7af66f18d034" +integrity = "664e731ff29c1f9c27d32391cbe45529ab812a71eed188cb235a1f37f84ee012" [[dependencies]] name = "forge-std" From c0a044b425938acbc65bb5c3c75dbc49eba044c6 Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Wed, 10 Jun 2026 13:46:48 +0100 Subject: [PATCH 3/5] feat(devnet): Add script to deploy UsdWithdrawalOutputBuilder through the factory. * refactor: base-deployment-script to use @openzeppelin-contracts version 5.2.0. --- packages/devnet/build.ts | 5 ++- .../devnet/script/BaseDeploymentScript.sol | 2 +- .../DeployUsdWithdrawalOutputBuilder.s.sol | 40 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol diff --git a/packages/devnet/build.ts b/packages/devnet/build.ts index 0b5da822..1e0f68a5 100644 --- a/packages/devnet/build.ts +++ b/packages/devnet/build.ts @@ -138,12 +138,11 @@ const perChainDeploymentTasks: ListrTask[] = Object.entries( * @returns */ const deploy = async (options: { privateKey: string; rpcUrl: string }) => { - // execute forge script const proc = Bun.spawn( [ "forge", "script", - "Deploy", + "DeployUsdWithdrawalOutputBuilder", "--broadcast", "--non-interactive", "--private-key", @@ -155,11 +154,13 @@ const deploy = async (options: { privateKey: string; rpcUrl: string }) => { { stdio: ["ignore", "pipe", "pipe"] }, ); const exitCode = await proc.exited; + if (exitCode !== 0) { throw new Error(`Forge script exited with code ${exitCode}`, { cause: await new Response(proc.stderr).text(), }); } + return exitCode; }; diff --git a/packages/devnet/script/BaseDeploymentScript.sol b/packages/devnet/script/BaseDeploymentScript.sol index 025d46d0..d30f8ead 100644 --- a/packages/devnet/script/BaseDeploymentScript.sol +++ b/packages/devnet/script/BaseDeploymentScript.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.8; -import {Create2} from "@openzeppelin-contracts-5.5.0/utils/Create2.sol"; +import {Create2} from "@openzeppelin-contracts-5.2.0/utils/Create2.sol"; import {Script} from "forge-std-1.9.6/src/Script.sol"; diff --git a/packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol b/packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol new file mode 100644 index 00000000..20badbd9 --- /dev/null +++ b/packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8; + +import {BaseDeploymentScript} from "./BaseDeploymentScript.sol"; +import {IERC20} from "@openzeppelin-contracts-5.2.0/token/ERC20/IERC20.sol"; +import {IUsdWithdrawalOutputBuilderFactory} from "cartesi-rollups-contracts-3.0.0-alpha.6/src/withdrawal/IUsdWithdrawalOutputBuilderFactory.sol"; + +contract DeployUsdWithdrawalOutputBuilder is BaseDeploymentScript { + bytes32 private constant SALT = bytes32(uint256(0)); + + function run() public { + address factoryAddress = _loadDeployment( + ".", + "UsdWithdrawalOutputBuilderFactory" + ); + IERC20 token = IERC20(_loadDeployment(".", "TestFungibleToken")); + + IUsdWithdrawalOutputBuilderFactory factory = + IUsdWithdrawalOutputBuilderFactory(factoryAddress); + + vm.startBroadcast(); + + address builder = factory.calculateUsdWithdrawalOutputBuilderAddress( + token, + SALT + ); + + // Keep this script idempotent when running against a state dump that + // may already contain this token+salt deployment. + if (builder.code.length == 0) { + address deployed = address(factory.newUsdWithdrawalOutputBuilder(token, SALT)); + vm.assertEq(deployed, builder); + builder = deployed; + } + + _storeDeployment("UsdWithdrawalOutputBuilder", builder); + + vm.stopBroadcast(); + } +} From 3e883b485b6ea7fd87da39dffbaab8f9f5d08f8b Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Wed, 10 Jun 2026 14:42:38 +0100 Subject: [PATCH 4/5] chore(devnet): include changeset for release --- .changeset/deep-sites-bake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/deep-sites-bake.md diff --git a/.changeset/deep-sites-bake.md b/.changeset/deep-sites-bake.md new file mode 100644 index 00000000..eaafff58 --- /dev/null +++ b/.changeset/deep-sites-bake.md @@ -0,0 +1,5 @@ +--- +"@cartesi/devnet": patch +--- + +Add a deployment script for UsdWithdrawalOutputBuilder to support emergency withdrawal tests. Remove solidity code and deployment script for test-tokens, instead it will package only the ones provided by rollups-contracts artifacts (devnet only). From 090e14c78a3bf8589b41e41c567be5889cf4033f Mon Sep 17 00:00:00 2001 From: Bruno Menezes Date: Thu, 11 Jun 2026 11:01:01 +0100 Subject: [PATCH 5/5] refactor(devnet): Prefix deployed contract with Test and adapt build script to support the change. --- packages/devnet/build.ts | 62 +++++++++++++++++-- ...eployTestUsdWithdrawalOutputBuilder.s.sol} | 4 +- 2 files changed, 60 insertions(+), 6 deletions(-) rename packages/devnet/script/{DeployUsdWithdrawalOutputBuilder.s.sol => DeployTestUsdWithdrawalOutputBuilder.s.sol} (90%) diff --git a/packages/devnet/build.ts b/packages/devnet/build.ts index 1e0f68a5..ae14a3c0 100644 --- a/packages/devnet/build.ts +++ b/packages/devnet/build.ts @@ -1,5 +1,5 @@ import { semver } from "bun"; -import { existsSync, readdirSync } from "fs-extra"; +import { cpSync, existsSync, readdirSync } from "fs-extra"; import { Listr, type ListrTask } from "listr2"; import * as path from "node:path"; import { @@ -135,14 +135,18 @@ const perChainDeploymentTasks: ListrTask[] = Object.entries( * @param options - The options for deploying contracts * @param options.privateKey - The private key to use for the deployment * @param options.rpcUrl - The RPC URL to use for the deployment - * @returns + * @returns - the exit code of the deployment script, 0 if successful + * @throws {Error} - if the deployment script exits with a non-zero code, the error message will contain the stderr output of the script */ -const deploy = async (options: { privateKey: string; rpcUrl: string }) => { +const runDeploymentScript = async ( + scriptName: string, + options: { privateKey: string; rpcUrl: string }, +) => { const proc = Bun.spawn( [ "forge", "script", - "DeployUsdWithdrawalOutputBuilder", + scriptName, "--broadcast", "--non-interactive", "--private-key", @@ -164,6 +168,56 @@ const deploy = async (options: { privateKey: string; rpcUrl: string }) => { return exitCode; }; +const deploy = async (options: { privateKey: string; rpcUrl: string }) => { + const contractName = "UsdWithdrawalOutputBuilder"; + + // Deploys an UsdWithdrawalOutputBuilder but saves the deployment information as TestUsdWithdrawalOutputBuilder. + const exitCode = await runDeploymentScript( + `DeployTest${contractName}`, + options, + ); + + if (exitCode === 0) { + // To continue the build process, we make sure there is a copy of the expected .sol folder + JSON content with added prefix + // in the name (i.e. Test[.sol, .json]) in the out folder. Therefore, the rest of the process can find the expected information + // and copy it to the right place. + const targetPath = path.join( + "out", + `${contractName}.sol`, + `${contractName}.json`, + ); + const destinationPath = path.join( + "out", + `Test${contractName}.sol`, + `Test${contractName}.json`, + ); + createCopy(targetPath, destinationPath); + } + + return exitCode; +}; + +/** + * Copy file or directory from source to destination, if source is a directory it will be copied recursively, + * if destination already exist it will be overwritten. + * @param source - path to file or directory to be copied + * @param destination - path to destination where file or directory should be copied + * @returns true if copy was successful, otherwise throws an error + * @throws {Error} if copy operation fails, the error message will contain the original error message + */ +const createCopy = (source: string, destination: string) => { + try { + cpSync(source, destination, { recursive: true, force: true }); + console.info(`Source copied from ${source} to ${destination}`); + } catch (error) { + throw new Error(`Failed to copy from ${source} to ${destination}`, { + cause: (error as Error).message, + }); + } + + return true; +}; + const build = async () => { type Ctx = { anvilProc?: Bun.Subprocess; diff --git a/packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol b/packages/devnet/script/DeployTestUsdWithdrawalOutputBuilder.s.sol similarity index 90% rename from packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol rename to packages/devnet/script/DeployTestUsdWithdrawalOutputBuilder.s.sol index 20badbd9..ce66f50c 100644 --- a/packages/devnet/script/DeployUsdWithdrawalOutputBuilder.s.sol +++ b/packages/devnet/script/DeployTestUsdWithdrawalOutputBuilder.s.sol @@ -5,7 +5,7 @@ import {BaseDeploymentScript} from "./BaseDeploymentScript.sol"; import {IERC20} from "@openzeppelin-contracts-5.2.0/token/ERC20/IERC20.sol"; import {IUsdWithdrawalOutputBuilderFactory} from "cartesi-rollups-contracts-3.0.0-alpha.6/src/withdrawal/IUsdWithdrawalOutputBuilderFactory.sol"; -contract DeployUsdWithdrawalOutputBuilder is BaseDeploymentScript { +contract DeployTestUsdWithdrawalOutputBuilder is BaseDeploymentScript { bytes32 private constant SALT = bytes32(uint256(0)); function run() public { @@ -33,7 +33,7 @@ contract DeployUsdWithdrawalOutputBuilder is BaseDeploymentScript { builder = deployed; } - _storeDeployment("UsdWithdrawalOutputBuilder", builder); + _storeDeployment("TestUsdWithdrawalOutputBuilder", builder); vm.stopBroadcast(); }