From 9d1dc511248f0fcd104ff84da5e85449c4c192b7 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Wed, 19 Aug 2026 19:15:59 -0300 Subject: [PATCH 1/3] fix(docker): binding generation in test image The image never copied script/contract-bindings.sh, which both contract justfiles have shelled out to since binding generation moved out of them, so `just bind` would die with exit 127 before reaching Forge. No CI workflow builds this image, so the gap survived the move. Give binding generation its own layer, without the artifact cache mounts. It runs `forge bind --force`, which clears out/ and cache/ before recompiling: removing a mount point fails with EBUSY, and the reduced compile would otherwise evict the artifacts the preceding step just built. --- test/Dockerfile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/Dockerfile b/test/Dockerfile index 20f039fc..d92ea6d9 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -114,13 +114,19 @@ RUN just -f cartesi-rollups/contracts/justfile install-deps COPY machine/step/ machine/step/ COPY justfile justfile -# builds smart contracts and generate Rust bindings for smart contracts. +# builds smart contracts. RUN \ --mount=type=cache,target=cartesi-rollups/contracts/out,sharing=locked \ --mount=type=cache,target=cartesi-rollups/contracts/cache,sharing=locked \ --mount=type=cache,target=prt/contracts/out,sharing=locked \ --mount=type=cache,target=prt/contracts/cache,sharing=locked \ - just build-smart-contracts && just bind + just build-smart-contracts + +# generates Rust bindings for smart contracts. +# no --mount here: `forge bind --force` clears out/ and cache/, which fails on a +# mount point, and its src-only compile would evict the artifacts built above. +COPY script/contract-bindings.sh script/contract-bindings.sh +RUN just bind # builds test programs and create anvil state dump. # requires compiled smart contracts. From 9bee20246f4471c2a22c058f3eecc239711b5ade Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 17 Aug 2026 16:27:23 -0300 Subject: [PATCH 2/3] fmt(contracts): single-line imports Foundry 1.5.0 introduced the `fmt.single_line_imports` Boolean configuration option. When set, imports of at most one item suffer no line breaks, even if they extend beyond the configured line length. This makes imports more readable. --- cartesi-rollups/contracts/foundry.toml | 1 + cartesi-rollups/contracts/src/DaveAppFactory.sol | 4 +--- cartesi-rollups/contracts/src/DaveConsensus.sol | 4 +--- cartesi-rollups/contracts/src/IDaveConsensus.sol | 4 +--- .../contracts/test/DaveAppFactory.t.sol | 8 ++------ prt/contracts/foundry.toml | 1 + prt/contracts/script/Deployment.s.sol | 12 +++--------- .../CanonicalTournamentParametersProvider.sol | 8 ++------ .../ITournamentParametersProvider.sol | 4 +--- prt/contracts/src/tournament/Tournament.sol | 4 +--- .../factories/IMultiLevelTournamentFactory.sol | 4 +--- .../factories/MultiLevelTournamentFactory.sol | 8 ++------ prt/contracts/src/tournament/libs/Commitment.sol | 4 +--- prt/contracts/src/types/Tree.sol | 4 +--- prt/contracts/test/StateTransition.t.sol | 4 +--- prt/contracts/test/StateTransitionFfi.t.sol | 4 +--- prt/contracts/test/Tournament.t.sol | 8 ++------ .../test/TournamentStateTransitionFfi.t.sol | 4 +--- prt/contracts/test/Util.sol | 16 ++++------------ .../test/accounting/RefundCallbacks.t.sol | 8 ++------ .../test/accounting/RefundReserve.t.sol | 8 ++------ .../HistoricalThreeLevelInner.t.sol | 8 ++------ .../HistoricalThreeLevelLeaf.t.sol | 12 +++--------- .../HistoricalThreeLevelRoot.t.sol | 8 ++------ .../config/CanonicalTournamentGeometry.t.sol | 12 +++--------- ...MultiLevelTournamentFactoryDependencies.t.sol | 8 ++------ .../TournamentParameterTableValidator.t.sol | 8 ++------ .../fixtures/ConfigurableCommitmentFixture.t.sol | 4 +--- .../fixtures/HistoricalThreeLevelGeometry.sol | 4 +--- .../test/fixtures/SmallFourLevelTournament.sol | 8 ++------ .../test/fixtures/SmallSingleLevelTournament.sol | 8 ++------ .../test/fixtures/SmallTwoLevelTournament.sol | 8 ++------ .../TournamentParameterTableValidator.sol | 4 +--- prt/contracts/test/gas/TournamentGas.t.sol | 12 +++--------- .../test/properties/BoundedOneLevelDelay.t.sol | 8 ++------ .../test/properties/LeafPopulationDelay.t.sol | 4 +--- .../TournamentLifecycleInvariant.t.sol | 4 +--- 37 files changed, 62 insertions(+), 180 deletions(-) diff --git a/cartesi-rollups/contracts/foundry.toml b/cartesi-rollups/contracts/foundry.toml index a46bb89a..7f930aa3 100644 --- a/cartesi-rollups/contracts/foundry.toml +++ b/cartesi-rollups/contracts/foundry.toml @@ -32,6 +32,7 @@ recursive_deps = true [fmt] sort_imports = true +single_line_imports = true [lint] exclude_lints = ["incorrect-shift"] diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index c759905e..1486b268 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -7,9 +7,7 @@ import {Create2} from "@openzeppelin-contracts-5.5.0/utils/Create2.sol"; import {DataAvailability} from "cartesi-rollups-contracts-3.0.0/src/common/DataAvailability.sol"; import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; -import { - IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {IOutputsMerkleRootValidator} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol"; import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index 20298bc4..2cc086a1 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -7,9 +7,7 @@ import {ERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/ERC165.s import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165.sol"; import {BitMaps} from "@openzeppelin-contracts-5.2.0/utils/structs/BitMaps.sol"; -import { - IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {IOutputsMerkleRootValidator} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; import {ApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/ApplicationChecker.sol"; import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; import {LibBinaryMerkleTree} from "cartesi-rollups-contracts-3.0.0/src/library/LibBinaryMerkleTree.sol"; diff --git a/cartesi-rollups/contracts/src/IDaveConsensus.sol b/cartesi-rollups/contracts/src/IDaveConsensus.sol index f6306788..73a91c66 100644 --- a/cartesi-rollups/contracts/src/IDaveConsensus.sol +++ b/cartesi-rollups/contracts/src/IDaveConsensus.sol @@ -4,9 +4,7 @@ pragma solidity ^0.8.30; import {BinaryMerkleTreeErrors} from "cartesi-rollups-contracts-3.0.0/src/common/BinaryMerkleTreeErrors.sol"; -import { - IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {IOutputsMerkleRootValidator} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationChecker.sol"; import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index d519ec8f..d278d71b 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -8,9 +8,7 @@ import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165 import {DataAvailability} from "cartesi-rollups-contracts-3.0.0/src/common/DataAvailability.sol"; import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; -import { - IOutputsMerkleRootValidator -} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; +import {IOutputsMerkleRootValidator} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; import {ApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/ApplicationFactory.sol"; import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationChecker.sol"; @@ -30,9 +28,7 @@ import {IDataProvider} from "prt-contracts/IDataProvider.sol"; import {IStateTransition} from "prt-contracts/IStateTransition.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; -import { - CanonicalTournamentParametersProvider -} from "prt-contracts/arbitration-config/CanonicalTournamentParametersProvider.sol"; +import {CanonicalTournamentParametersProvider} from "prt-contracts/arbitration-config/CanonicalTournamentParametersProvider.sol"; import {CartesiStateTransition} from "prt-contracts/state-transition/CartesiStateTransition.sol"; import {Tournament} from "prt-contracts/tournament/Tournament.sol"; import {MultiLevelTournamentFactory} from "prt-contracts/tournament/factories/MultiLevelTournamentFactory.sol"; diff --git a/prt/contracts/foundry.toml b/prt/contracts/foundry.toml index d4920511..7b642418 100644 --- a/prt/contracts/foundry.toml +++ b/prt/contracts/foundry.toml @@ -32,6 +32,7 @@ exclude_lints = ["incorrect-shift"] line_length = 80 tab_width = 4 sort_imports = true +single_line_imports = true [dependencies] forge-std = "1.9.6" diff --git a/prt/contracts/script/Deployment.s.sol b/prt/contracts/script/Deployment.s.sol index 2f16be39..4374ec34 100644 --- a/prt/contracts/script/Deployment.s.sol +++ b/prt/contracts/script/Deployment.s.sol @@ -5,16 +5,10 @@ pragma solidity ^0.8.8; import {BaseDeploymentScript} from "./BaseDeploymentScript.sol"; -import { - CanonicalTournamentParametersProvider -} from "src/arbitration-config/CanonicalTournamentParametersProvider.sol"; -import { - CartesiStateTransition -} from "src/state-transition/CartesiStateTransition.sol"; +import {CanonicalTournamentParametersProvider} from "src/arbitration-config/CanonicalTournamentParametersProvider.sol"; +import {CartesiStateTransition} from "src/state-transition/CartesiStateTransition.sol"; import {Tournament} from "src/tournament/Tournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Time} from "src/tournament/libs/Time.sol"; type Milliseconds is uint64; diff --git a/prt/contracts/src/arbitration-config/CanonicalTournamentParametersProvider.sol b/prt/contracts/src/arbitration-config/CanonicalTournamentParametersProvider.sol index 5f20533f..c0769fcd 100644 --- a/prt/contracts/src/arbitration-config/CanonicalTournamentParametersProvider.sol +++ b/prt/contracts/src/arbitration-config/CanonicalTournamentParametersProvider.sol @@ -4,13 +4,9 @@ pragma solidity ^0.8.17; import {ArbitrationConstants} from "./ArbitrationConstants.sol"; -import { - ITournamentParametersProvider -} from "./ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "./ITournamentParametersProvider.sol"; import {Time} from "prt-contracts/tournament/libs/Time.sol"; -import { - TournamentParameters -} from "prt-contracts/types/TournamentParameters.sol"; +import {TournamentParameters} from "prt-contracts/types/TournamentParameters.sol"; contract CanonicalTournamentParametersProvider is ITournamentParametersProvider diff --git a/prt/contracts/src/arbitration-config/ITournamentParametersProvider.sol b/prt/contracts/src/arbitration-config/ITournamentParametersProvider.sol index de1d20f0..97974e4a 100644 --- a/prt/contracts/src/arbitration-config/ITournamentParametersProvider.sol +++ b/prt/contracts/src/arbitration-config/ITournamentParametersProvider.sol @@ -3,9 +3,7 @@ pragma solidity ^0.8.17; -import { - TournamentParameters -} from "prt-contracts/types/TournamentParameters.sol"; +import {TournamentParameters} from "prt-contracts/types/TournamentParameters.sol"; interface ITournamentParametersProvider { /// @notice Get tournament parameters for a given level. diff --git a/prt/contracts/src/tournament/Tournament.sol b/prt/contracts/src/tournament/Tournament.sol index 6b12375d..c652f147 100644 --- a/prt/contracts/src/tournament/Tournament.sol +++ b/prt/contracts/src/tournament/Tournament.sol @@ -8,9 +8,7 @@ import {Math} from "@openzeppelin-contracts-5.5.0/utils/math/Math.sol"; import {IStateTransition} from "prt-contracts/IStateTransition.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; -import { - IMultiLevelTournamentFactory -} from "prt-contracts/tournament/factories/IMultiLevelTournamentFactory.sol"; +import {IMultiLevelTournamentFactory} from "prt-contracts/tournament/factories/IMultiLevelTournamentFactory.sol"; import {Bond} from "prt-contracts/tournament/libs/Bond.sol"; import {Clock} from "prt-contracts/tournament/libs/Clock.sol"; import {Commitment} from "prt-contracts/tournament/libs/Commitment.sol"; diff --git a/prt/contracts/src/tournament/factories/IMultiLevelTournamentFactory.sol b/prt/contracts/src/tournament/factories/IMultiLevelTournamentFactory.sol index a26f1a67..f985ca90 100644 --- a/prt/contracts/src/tournament/factories/IMultiLevelTournamentFactory.sol +++ b/prt/contracts/src/tournament/factories/IMultiLevelTournamentFactory.sol @@ -9,9 +9,7 @@ import {ITournament} from "prt-contracts/ITournament.sol"; import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol"; import {Time} from "prt-contracts/tournament/libs/Time.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; -import { - TournamentParameters -} from "prt-contracts/types/TournamentParameters.sol"; +import {TournamentParameters} from "prt-contracts/types/TournamentParameters.sol"; import {Tree} from "prt-contracts/types/Tree.sol"; interface IMultiLevelTournamentFactory is ITournamentFactory { diff --git a/prt/contracts/src/tournament/factories/MultiLevelTournamentFactory.sol b/prt/contracts/src/tournament/factories/MultiLevelTournamentFactory.sol index 0ee8dab6..1a085606 100644 --- a/prt/contracts/src/tournament/factories/MultiLevelTournamentFactory.sol +++ b/prt/contracts/src/tournament/factories/MultiLevelTournamentFactory.sol @@ -10,16 +10,12 @@ import {IMultiLevelTournamentFactory} from "./IMultiLevelTournamentFactory.sol"; import {IDataProvider} from "prt-contracts/IDataProvider.sol"; import {IStateTransition} from "prt-contracts/IStateTransition.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; -import { - ITournamentParametersProvider -} from "prt-contracts/arbitration-config/ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "prt-contracts/arbitration-config/ITournamentParametersProvider.sol"; import {Tournament} from "prt-contracts/tournament/Tournament.sol"; import {Commitment} from "prt-contracts/tournament/libs/Commitment.sol"; import {Time} from "prt-contracts/tournament/libs/Time.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; -import { - TournamentParameters -} from "prt-contracts/types/TournamentParameters.sol"; +import {TournamentParameters} from "prt-contracts/types/TournamentParameters.sol"; import {Tree} from "prt-contracts/types/Tree.sol"; /// @dev The immutable provider is the sole authority for a parameter table diff --git a/prt/contracts/src/tournament/libs/Commitment.sol b/prt/contracts/src/tournament/libs/Commitment.sol index 30d3f2f9..fa946783 100644 --- a/prt/contracts/src/tournament/libs/Commitment.sol +++ b/prt/contracts/src/tournament/libs/Commitment.sol @@ -3,9 +3,7 @@ pragma solidity ^0.8.17; -import { - Hashes -} from "@openzeppelin-contracts-5.5.0/utils/cryptography/Hashes.sol"; +import {Hashes} from "@openzeppelin-contracts-5.5.0/utils/cryptography/Hashes.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; diff --git a/prt/contracts/src/types/Tree.sol b/prt/contracts/src/types/Tree.sol index 358b1a95..f3fd6bd3 100644 --- a/prt/contracts/src/types/Tree.sol +++ b/prt/contracts/src/types/Tree.sol @@ -3,9 +3,7 @@ pragma solidity ^0.8.17; -import { - Hashes -} from "@openzeppelin-contracts-5.5.0/utils/cryptography/Hashes.sol"; +import {Hashes} from "@openzeppelin-contracts-5.5.0/utils/cryptography/Hashes.sol"; import {ITournament} from "prt-contracts/ITournament.sol"; import {Machine} from "prt-contracts/types/Machine.sol"; diff --git a/prt/contracts/test/StateTransition.t.sol b/prt/contracts/test/StateTransition.t.sol index 18dd1a6e..ce6534a0 100644 --- a/prt/contracts/test/StateTransition.t.sol +++ b/prt/contracts/test/StateTransition.t.sol @@ -18,9 +18,7 @@ import {EmulatorConstants} from "step/src/EmulatorConstants.sol"; import {Memory} from "step/src/Memory.sol"; import {IDataProvider} from "src/IDataProvider.sol"; -import { - CartesiStateTransition -} from "src/state-transition/CartesiStateTransition.sol"; +import {CartesiStateTransition} from "src/state-transition/CartesiStateTransition.sol"; import {Util} from "./Util.sol"; diff --git a/prt/contracts/test/StateTransitionFfi.t.sol b/prt/contracts/test/StateTransitionFfi.t.sol index 07d0fced..aed71f53 100644 --- a/prt/contracts/test/StateTransitionFfi.t.sol +++ b/prt/contracts/test/StateTransitionFfi.t.sol @@ -15,9 +15,7 @@ pragma solidity ^0.8.0; import {EmulatorConstants} from "step/src/EmulatorConstants.sol"; import {IDataProvider} from "src/IDataProvider.sol"; -import { - CartesiStateTransition -} from "src/state-transition/CartesiStateTransition.sol"; +import {CartesiStateTransition} from "src/state-transition/CartesiStateTransition.sol"; import {Util} from "./Util.sol"; diff --git a/prt/contracts/test/Tournament.t.sol b/prt/contracts/test/Tournament.t.sol index f807ee76..b2e2c9bb 100644 --- a/prt/contracts/test/Tournament.t.sol +++ b/prt/contracts/test/Tournament.t.sol @@ -14,9 +14,7 @@ pragma solidity ^0.8.0; import {IStateTransition} from "src/IStateTransition.sol"; import {ITournament} from "src/ITournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Clock} from "src/tournament/libs/Clock.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {MatchClocks} from "src/tournament/libs/MatchClocks.sol"; @@ -25,9 +23,7 @@ import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; import {Util} from "./Util.sol"; -import { - HistoricalThreeLevelGeometry as HistoricalGeometry -} from "./fixtures/HistoricalThreeLevelGeometry.sol"; +import {HistoricalThreeLevelGeometry as HistoricalGeometry} from "./fixtures/HistoricalThreeLevelGeometry.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/TournamentStateTransitionFfi.t.sol b/prt/contracts/test/TournamentStateTransitionFfi.t.sol index 545b20e0..4316bc9f 100644 --- a/prt/contracts/test/TournamentStateTransitionFfi.t.sol +++ b/prt/contracts/test/TournamentStateTransitionFfi.t.sol @@ -5,9 +5,7 @@ pragma solidity ^0.8.22; import {IDataProvider} from "src/IDataProvider.sol"; import {ITournament} from "src/ITournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; diff --git a/prt/contracts/test/Util.sol b/prt/contracts/test/Util.sol index 51333326..f8a2829a 100644 --- a/prt/contracts/test/Util.sol +++ b/prt/contracts/test/Util.sol @@ -16,19 +16,11 @@ import {Test} from "forge-std-1.9.6/src/Test.sol"; import {IDataProvider} from "src/IDataProvider.sol"; import {ITournament} from "src/ITournament.sol"; -import { - CanonicalTournamentParametersProvider -} from "src/arbitration-config/CanonicalTournamentParametersProvider.sol"; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; -import { - CartesiStateTransition -} from "src/state-transition/CartesiStateTransition.sol"; +import {CanonicalTournamentParametersProvider} from "src/arbitration-config/CanonicalTournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {CartesiStateTransition} from "src/state-transition/CartesiStateTransition.sol"; import {Tournament} from "src/tournament/Tournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {Machine} from "src/types/Machine.sol"; diff --git a/prt/contracts/test/accounting/RefundCallbacks.t.sol b/prt/contracts/test/accounting/RefundCallbacks.t.sol index ed4869bd..24c77fca 100644 --- a/prt/contracts/test/accounting/RefundCallbacks.t.sol +++ b/prt/contracts/test/accounting/RefundCallbacks.t.sol @@ -9,13 +9,9 @@ import {Vm} from "forge-std-1.9.6/src/Vm.sol"; import {IDataProvider} from "src/IDataProvider.sol"; import {IStateTransition} from "src/IStateTransition.sol"; import {ITournament} from "src/ITournament.sol"; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; import {Tournament} from "src/tournament/Tournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Bond} from "src/tournament/libs/Bond.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {Time} from "src/tournament/libs/Time.sol"; diff --git a/prt/contracts/test/accounting/RefundReserve.t.sol b/prt/contracts/test/accounting/RefundReserve.t.sol index d5128317..80bfb21b 100644 --- a/prt/contracts/test/accounting/RefundReserve.t.sol +++ b/prt/contracts/test/accounting/RefundReserve.t.sol @@ -8,13 +8,9 @@ import {Test} from "forge-std-1.9.6/src/Test.sol"; import {IDataProvider} from "src/IDataProvider.sol"; import {IStateTransition} from "src/IStateTransition.sol"; import {ITournament} from "src/ITournament.sol"; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; import {Tournament} from "src/tournament/Tournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Bond} from "src/tournament/libs/Bond.sol"; import {Clock} from "src/tournament/libs/Clock.sol"; import {Gas} from "src/tournament/libs/Gas.sol"; diff --git a/prt/contracts/test/characterization/HistoricalThreeLevelInner.t.sol b/prt/contracts/test/characterization/HistoricalThreeLevelInner.t.sol index be86679c..1d8e7430 100644 --- a/prt/contracts/test/characterization/HistoricalThreeLevelInner.t.sol +++ b/prt/contracts/test/characterization/HistoricalThreeLevelInner.t.sol @@ -15,9 +15,7 @@ pragma solidity ^0.8.0; import {Vm} from "forge-std-1.9.6/src/Vm.sol"; import {ITournament} from "src/ITournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Clock} from "src/tournament/libs/Clock.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {Time} from "src/tournament/libs/Time.sol"; @@ -25,9 +23,7 @@ import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; import {Util} from "../Util.sol"; -import { - HistoricalThreeLevelGeometry as HistoricalGeometry -} from "../fixtures/HistoricalThreeLevelGeometry.sol"; +import {HistoricalThreeLevelGeometry as HistoricalGeometry} from "../fixtures/HistoricalThreeLevelGeometry.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/characterization/HistoricalThreeLevelLeaf.t.sol b/prt/contracts/test/characterization/HistoricalThreeLevelLeaf.t.sol index ceb520f3..a010ccba 100644 --- a/prt/contracts/test/characterization/HistoricalThreeLevelLeaf.t.sol +++ b/prt/contracts/test/characterization/HistoricalThreeLevelLeaf.t.sol @@ -17,12 +17,8 @@ import {Vm} from "forge-std-1.9.6/src/Vm.sol"; import {IDataProvider} from "src/IDataProvider.sol"; import {IStateTransition} from "src/IStateTransition.sol"; import {ITournament} from "src/ITournament.sol"; -import { - CartesiStateTransition -} from "src/state-transition/CartesiStateTransition.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {CartesiStateTransition} from "src/state-transition/CartesiStateTransition.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Clock} from "src/tournament/libs/Clock.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {Time} from "src/tournament/libs/Time.sol"; @@ -30,9 +26,7 @@ import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; import {Util} from "../Util.sol"; -import { - HistoricalThreeLevelGeometry as HistoricalGeometry -} from "../fixtures/HistoricalThreeLevelGeometry.sol"; +import {HistoricalThreeLevelGeometry as HistoricalGeometry} from "../fixtures/HistoricalThreeLevelGeometry.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/characterization/HistoricalThreeLevelRoot.t.sol b/prt/contracts/test/characterization/HistoricalThreeLevelRoot.t.sol index 3bf4a924..eb533045 100644 --- a/prt/contracts/test/characterization/HistoricalThreeLevelRoot.t.sol +++ b/prt/contracts/test/characterization/HistoricalThreeLevelRoot.t.sol @@ -14,18 +14,14 @@ pragma solidity ^0.8.0; import {IDataProvider} from "src/IDataProvider.sol"; import {ITournament} from "src/ITournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Match} from "src/tournament/libs/Match.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; import {Util} from "../Util.sol"; -import { - HistoricalThreeLevelGeometry as HistoricalGeometry -} from "../fixtures/HistoricalThreeLevelGeometry.sol"; +import {HistoricalThreeLevelGeometry as HistoricalGeometry} from "../fixtures/HistoricalThreeLevelGeometry.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/config/CanonicalTournamentGeometry.t.sol b/prt/contracts/test/config/CanonicalTournamentGeometry.t.sol index 1236262b..fd618a10 100644 --- a/prt/contracts/test/config/CanonicalTournamentGeometry.t.sol +++ b/prt/contracts/test/config/CanonicalTournamentGeometry.t.sol @@ -14,15 +14,9 @@ pragma solidity ^0.8.17; import {IDataProvider} from "src/IDataProvider.sol"; import {ITournament} from "src/ITournament.sol"; -import { - ArbitrationConstants -} from "src/arbitration-config/ArbitrationConstants.sol"; -import { - CanonicalTournamentParametersProvider -} from "src/arbitration-config/CanonicalTournamentParametersProvider.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {ArbitrationConstants} from "src/arbitration-config/ArbitrationConstants.sol"; +import {CanonicalTournamentParametersProvider} from "src/arbitration-config/CanonicalTournamentParametersProvider.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; diff --git a/prt/contracts/test/config/MultiLevelTournamentFactoryDependencies.t.sol b/prt/contracts/test/config/MultiLevelTournamentFactoryDependencies.t.sol index e360a61b..09b94320 100644 --- a/prt/contracts/test/config/MultiLevelTournamentFactoryDependencies.t.sol +++ b/prt/contracts/test/config/MultiLevelTournamentFactoryDependencies.t.sol @@ -10,13 +10,9 @@ import {Errors} from "@openzeppelin-contracts-5.5.0/utils/Errors.sol"; import {IDataProvider} from "src/IDataProvider.sol"; import {IStateTransition} from "src/IStateTransition.sol"; import {ITournament} from "src/ITournament.sol"; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; import {Tournament} from "src/tournament/Tournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {Machine} from "src/types/Machine.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; diff --git a/prt/contracts/test/config/TournamentParameterTableValidator.t.sol b/prt/contracts/test/config/TournamentParameterTableValidator.t.sol index 62eb10c8..fbd05218 100644 --- a/prt/contracts/test/config/TournamentParameterTableValidator.t.sol +++ b/prt/contracts/test/config/TournamentParameterTableValidator.t.sol @@ -5,13 +5,9 @@ pragma solidity ^0.8.17; import {Test} from "forge-std-1.9.6/src/Test.sol"; -import { - ArbitrationConstants -} from "prt-contracts/arbitration-config/ArbitrationConstants.sol"; +import {ArbitrationConstants} from "prt-contracts/arbitration-config/ArbitrationConstants.sol"; import {Time} from "prt-contracts/tournament/libs/Time.sol"; -import { - TournamentParameters -} from "prt-contracts/types/TournamentParameters.sol"; +import {TournamentParameters} from "prt-contracts/types/TournamentParameters.sol"; import { TournamentParameterTableValidator, diff --git a/prt/contracts/test/fixtures/ConfigurableCommitmentFixture.t.sol b/prt/contracts/test/fixtures/ConfigurableCommitmentFixture.t.sol index 04284eac..27b88d9c 100644 --- a/prt/contracts/test/fixtures/ConfigurableCommitmentFixture.t.sol +++ b/prt/contracts/test/fixtures/ConfigurableCommitmentFixture.t.sol @@ -9,9 +9,7 @@ import {Commitment} from "src/tournament/libs/Commitment.sol"; import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; -import { - ConfigurableCommitmentFixture -} from "./ConfigurableCommitmentFixture.sol"; +import {ConfigurableCommitmentFixture} from "./ConfigurableCommitmentFixture.sol"; contract CommitmentProofVerifier { function rootAt( diff --git a/prt/contracts/test/fixtures/HistoricalThreeLevelGeometry.sol b/prt/contracts/test/fixtures/HistoricalThreeLevelGeometry.sol index 6c320804..ab83e8e6 100644 --- a/prt/contracts/test/fixtures/HistoricalThreeLevelGeometry.sol +++ b/prt/contracts/test/fixtures/HistoricalThreeLevelGeometry.sol @@ -3,9 +3,7 @@ pragma solidity ^0.8.17; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; diff --git a/prt/contracts/test/fixtures/SmallFourLevelTournament.sol b/prt/contracts/test/fixtures/SmallFourLevelTournament.sol index 0e1c94c4..f721428b 100644 --- a/prt/contracts/test/fixtures/SmallFourLevelTournament.sol +++ b/prt/contracts/test/fixtures/SmallFourLevelTournament.sol @@ -3,12 +3,8 @@ pragma solidity ^0.8.17; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {Machine} from "src/types/Machine.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; diff --git a/prt/contracts/test/fixtures/SmallSingleLevelTournament.sol b/prt/contracts/test/fixtures/SmallSingleLevelTournament.sol index 67624822..49101cea 100644 --- a/prt/contracts/test/fixtures/SmallSingleLevelTournament.sol +++ b/prt/contracts/test/fixtures/SmallSingleLevelTournament.sol @@ -3,12 +3,8 @@ pragma solidity ^0.8.17; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; diff --git a/prt/contracts/test/fixtures/SmallTwoLevelTournament.sol b/prt/contracts/test/fixtures/SmallTwoLevelTournament.sol index bd1b0d46..95248387 100644 --- a/prt/contracts/test/fixtures/SmallTwoLevelTournament.sol +++ b/prt/contracts/test/fixtures/SmallTwoLevelTournament.sol @@ -3,12 +3,8 @@ pragma solidity ^0.8.17; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Time} from "src/tournament/libs/Time.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; diff --git a/prt/contracts/test/fixtures/TournamentParameterTableValidator.sol b/prt/contracts/test/fixtures/TournamentParameterTableValidator.sol index 0534b9c7..d5c34766 100644 --- a/prt/contracts/test/fixtures/TournamentParameterTableValidator.sol +++ b/prt/contracts/test/fixtures/TournamentParameterTableValidator.sol @@ -4,9 +4,7 @@ pragma solidity ^0.8.17; import {Time} from "prt-contracts/tournament/libs/Time.sol"; -import { - TournamentParameters -} from "prt-contracts/types/TournamentParameters.sol"; +import {TournamentParameters} from "prt-contracts/types/TournamentParameters.sol"; /// @notice Test-only validation for an injected tournament parameter table. /// @dev Production keeps geometry static. This helper makes the generation diff --git a/prt/contracts/test/gas/TournamentGas.t.sol b/prt/contracts/test/gas/TournamentGas.t.sol index 092d626d..f85b2886 100644 --- a/prt/contracts/test/gas/TournamentGas.t.sol +++ b/prt/contracts/test/gas/TournamentGas.t.sol @@ -9,13 +9,9 @@ import {Vm} from "forge-std-1.9.6/src/Vm.sol"; import {IDataProvider} from "src/IDataProvider.sol"; import {IStateTransition} from "src/IStateTransition.sol"; import {ITournament} from "src/ITournament.sol"; -import { - ITournamentParametersProvider -} from "src/arbitration-config/ITournamentParametersProvider.sol"; +import {ITournamentParametersProvider} from "src/arbitration-config/ITournamentParametersProvider.sol"; import {Tournament} from "src/tournament/Tournament.sol"; -import { - MultiLevelTournamentFactory -} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; +import {MultiLevelTournamentFactory} from "src/tournament/factories/MultiLevelTournamentFactory.sol"; import {Bond} from "src/tournament/libs/Bond.sol"; import {Clock} from "src/tournament/libs/Clock.sol"; import {Gas} from "src/tournament/libs/Gas.sol"; @@ -25,9 +21,7 @@ import {Machine} from "src/types/Machine.sol"; import {TournamentParameters} from "src/types/TournamentParameters.sol"; import {Tree} from "src/types/Tree.sol"; -import { - ConfigurableCommitmentFixture -} from "../fixtures/ConfigurableCommitmentFixture.sol"; +import {ConfigurableCommitmentFixture} from "../fixtures/ConfigurableCommitmentFixture.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/properties/BoundedOneLevelDelay.t.sol b/prt/contracts/test/properties/BoundedOneLevelDelay.t.sol index cd1f8e6d..5fa704de 100644 --- a/prt/contracts/test/properties/BoundedOneLevelDelay.t.sol +++ b/prt/contracts/test/properties/BoundedOneLevelDelay.t.sol @@ -13,14 +13,10 @@ import {Time} from "src/tournament/libs/Time.sol"; import {Machine} from "src/types/Machine.sol"; import {Tree} from "src/types/Tree.sol"; -import { - BoundedOneLevelDelayModel -} from "../fixtures/BoundedOneLevelDelayModel.sol"; +import {BoundedOneLevelDelayModel} from "../fixtures/BoundedOneLevelDelayModel.sol"; import {InspectableTournament} from "../fixtures/InspectableTournament.sol"; import {SmallFullTree} from "../fixtures/SmallFullTree.sol"; -import { - SmallSingleLevelTournamentFactory -} from "../fixtures/SmallSingleLevelTournament.sol"; +import {SmallSingleLevelTournamentFactory} from "../fixtures/SmallSingleLevelTournament.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/properties/LeafPopulationDelay.t.sol b/prt/contracts/test/properties/LeafPopulationDelay.t.sol index 685a88cf..3743eb79 100644 --- a/prt/contracts/test/properties/LeafPopulationDelay.t.sol +++ b/prt/contracts/test/properties/LeafPopulationDelay.t.sol @@ -15,9 +15,7 @@ import {Tree} from "src/types/Tree.sol"; import {InspectableTournament} from "../fixtures/InspectableTournament.sol"; import {SmallFullTree} from "../fixtures/SmallFullTree.sol"; -import { - SmallSingleLevelTournamentFactory -} from "../fixtures/SmallSingleLevelTournament.sol"; +import {SmallSingleLevelTournamentFactory} from "../fixtures/SmallSingleLevelTournament.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; diff --git a/prt/contracts/test/properties/TournamentLifecycleInvariant.t.sol b/prt/contracts/test/properties/TournamentLifecycleInvariant.t.sol index 737994b6..52bf009b 100644 --- a/prt/contracts/test/properties/TournamentLifecycleInvariant.t.sol +++ b/prt/contracts/test/properties/TournamentLifecycleInvariant.t.sol @@ -16,9 +16,7 @@ import {Tree} from "src/types/Tree.sol"; import {InspectableTournament} from "../fixtures/InspectableTournament.sol"; import {SmallFullTree} from "../fixtures/SmallFullTree.sol"; -import { - SmallSingleLevelTournamentFactory -} from "../fixtures/SmallSingleLevelTournament.sol"; +import {SmallSingleLevelTournamentFactory} from "../fixtures/SmallSingleLevelTournament.sol"; import {TournamentInspector} from "test/fixtures/TournamentInspector.sol"; From 2cf4536f02862e19bf315ba75108c678042908e5 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 17 Aug 2026 17:07:14 -0300 Subject: [PATCH 3/3] feat(contracts): bump rollups-contracts to alpha 9 This commit bumps rollups-contracts from 3.0.0-alpha.6 to 3.0.0-alpha.9 and makes the necessary adjustments on the contracts side. Implement DaveConsensus.wasInputFinalized, which is necessary under the new IOutputsMerkleRootValidator interface and used in deposit refunds. This commit also adds unit tests for this new function. The implementation is trivial, as it only needs to compare the provided input index with the input index inclusive lower bound of the current sealed epoch. The provided input block number is discarded entirely by DaveConsensus, similar to how the provided input index is discarded entirely by both Authority and Quorum, since they delimit epochs based on input block numbers rather than input indices. Adapt DaveAppFactory implementation to changes in IApplicationFactory interface relative to the data availability blob being replaced by IInputBox. The DaveAppFactory contract already stored an immutable IInputBox value, and encoded the DA blob at runtime. Now, it just needs to forward this immutable value to IApplicationFactory. This is an internal change. Users of the IDaveAppFactory interface should not notice any difference (if not for the new ApplicationCreated event). Adapt contract deployment artifact loading/storing utilities to store deployments in both TXT and JSON formats for now (JSON is deprecated for now so that clients can smoothly migrate to TXT), load individual deployments from TXT artifacts, and import project deployments in both formats. These changes are internal to BaseDeploymentScript. The signature of the store/load/import functions haven't changed. We also took the opportunity to edit the is-contract-name-valid function so that it uses literal character strings rather than hard-coded ASCII codes. It also adapts internal clients of deployment artifacts, giving preference to TXT deployment artifacts (given that JSON deployment artifacts are currently deprecated and will be removed in the future). So, instead of doing `jq -r .address`, you'd just `cat` the file. In the blockchain-reader tests, we can now load deployments without serde, which allowed us to remove it as dependency from that crate. This commit also makes the necessary ERC to Erc renamings. For example, the ERC20Portal was renamed to Erc20Portal, and its depositERC20Tokens was renamed to depositErc20Tokens. Internally, we now use rollups-contracts deployment fixture, so that we don't have to repeat ourselves on the test contracts. This required an upstream fix on rollups-contracts regarding import paths being absolute (like `src/`) rather than relative (like `../../src/`). We also had to set always_use_create_2_factory so that the fixture would work in tests. --- Cargo.lock | 1 - cartesi-rollups/contracts/foundry.toml | 13 +- .../contracts/script/Deployment.s.sol | 2 +- .../contracts/script/build-devnet.sh | 2 +- cartesi-rollups/contracts/script/deploy.sh | 2 +- cartesi-rollups/contracts/soldeer.lock | 8 +- .../contracts/src/DaveAppFactory.sol | 17 +- .../contracts/src/DaveConsensus.sol | 10 + .../contracts/test/DaveAppFactory.t.sol | 208 ++++++++++++++--- cartesi-rollups/node/Cargo.toml | 1 - .../node/src/blockchain_reader/test_utils.rs | 13 +- prt/contracts/script/BaseDeploymentScript.sol | 214 +++++++++++++++--- prt/contracts/script/Deployment.s.sol | 4 +- script/devnet-fingerprint.sh | 2 +- script/machine-image-fingerprint.sh | 10 +- script/tests/devnet-fingerprint.sh | 8 +- test/e2e/rollups/justfile | 8 +- .../rollups/scenarios/deposit_withdrawal.lua | 2 +- .../generate-devnet-honeypot-config.sh | 4 +- 19 files changed, 408 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0d214bc..54007370 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1736,7 +1736,6 @@ dependencies = [ "ruint", "rusqlite", "rusqlite_migration", - "serde", "serde_json", "tempfile", "testcontainers-modules", diff --git a/cartesi-rollups/contracts/foundry.toml b/cartesi-rollups/contracts/foundry.toml index 7f930aa3..f0a105ec 100644 --- a/cartesi-rollups/contracts/foundry.toml +++ b/cartesi-rollups/contracts/foundry.toml @@ -8,21 +8,22 @@ via_ir = true allow_paths = ["../../prt/contracts", "../../machine/step"] remappings = [ - "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/dependencies/@openzeppelin-contracts-5.2.0/", + "@openzeppelin-contracts-5.2.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.9/dependencies/@openzeppelin-contracts-5.2.0/", "@openzeppelin-contracts-5.5.0/=dependencies/@openzeppelin-contracts-5.5.0/", - "cartesi-machine-solidity-step-0.13.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/dependencies/cartesi-machine-solidity-step-0.13.0/", - "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/", + "cartesi-machine-solidity-step-0.15.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.9/dependencies/cartesi-machine-solidity-step-0.15.0/", + "cartesi-rollups-contracts-3.0.0/=dependencies/cartesi-rollups-contracts-3.0.0-alpha.9/", "forge-std-1.9.6/=dependencies/forge-std-1.9.6/", - "prt-contracts/=../../prt/contracts/src/", "prt-contracts-test/=../../prt/contracts/test/", + "prt-contracts/=../../prt/contracts/src/", "step/=../../machine/step/", ] solc_version = "0.8.30" evm_version = "prague" +always_use_create_2_factory = true fs_permissions = [ { access = "read-write", path = "deployments" }, - { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.6/deployments" }, + { access = "read", path = "dependencies/cartesi-rollups-contracts-3.0.0-alpha.9/deployments" }, { access = "read", path = "../../prt/contracts/deployments" }, ] @@ -40,4 +41,4 @@ exclude_lints = ["incorrect-shift"] [dependencies] "@openzeppelin-contracts" = "5.5.0" forge-std = "1.9.6" -cartesi-rollups-contracts = "3.0.0-alpha.6" +cartesi-rollups-contracts = "3.0.0-alpha.9" diff --git a/cartesi-rollups/contracts/script/Deployment.s.sol b/cartesi-rollups/contracts/script/Deployment.s.sol index 7de8a6e4..b5e55b02 100644 --- a/cartesi-rollups/contracts/script/Deployment.s.sol +++ b/cartesi-rollups/contracts/script/Deployment.s.sol @@ -10,7 +10,7 @@ import {DaveAppFactory} from "src/DaveAppFactory.sol"; contract DeploymentScript is BaseDeploymentScript { function run() external { _importDeployments("../../prt/contracts"); - _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.6"); + _importDeployments("dependencies/cartesi-rollups-contracts-3.0.0-alpha.9"); address inputBox = _loadDeployment(".", "InputBox"); address appFactory = _loadDeployment(".", "ApplicationFactory"); diff --git a/cartesi-rollups/contracts/script/build-devnet.sh b/cartesi-rollups/contracts/script/build-devnet.sh index a56ba5c5..7d94a590 100755 --- a/cartesi-rollups/contracts/script/build-devnet.sh +++ b/cartesi-rollups/contracts/script/build-devnet.sh @@ -6,7 +6,7 @@ cd "${BASH_SOURCE%/*}/.." deployment_complete=0 build_fingerprint=$(../../script/devnet-fingerprint.sh inputs) -base_contracts=dependencies/cartesi-rollups-contracts-3.0.0-alpha.6 +base_contracts=dependencies/cartesi-rollups-contracts-3.0.0-alpha.9 cleanup() { exit_code=$? diff --git a/cartesi-rollups/contracts/script/deploy.sh b/cartesi-rollups/contracts/script/deploy.sh index 843d5f70..7bc0c164 100755 --- a/cartesi-rollups/contracts/script/deploy.sh +++ b/cartesi-rollups/contracts/script/deploy.sh @@ -6,7 +6,7 @@ cd "${BASH_SOURCE%/*}/.." roots=( '../../prt/contracts' - 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.6' + 'dependencies/cartesi-rollups-contracts-3.0.0-alpha.9' '.' ) diff --git a/cartesi-rollups/contracts/soldeer.lock b/cartesi-rollups/contracts/soldeer.lock index 7e7a34fc..f7d87c85 100644 --- a/cartesi-rollups/contracts/soldeer.lock +++ b/cartesi-rollups/contracts/soldeer.lock @@ -7,10 +7,10 @@ integrity = "da8336cf949f0e0667ae8360af849681e3a3e76d7e61e7a86b1a3414a158aeea" [[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 = "6fd07e7d895795aca4877d1c0f357f19f88adc56e1ff1ff88950900391328969" +version = "3.0.0-alpha.9" +url = "https://soldeer-revisions.s3.amazonaws.com/cartesi-rollups-contracts/3_0_0-alpha_9_19-08-2026_11:52:19_rollups-contracts.zip" +checksum = "c17618f6aa3313ce8e28f043877b8e16695b358ccf0f77ecca19b234718ebf16" +integrity = "f606c1db57d8b6ff0de8a73ec8b7d762e79d3b06959554e748663f03d0b1f588" [[dependencies]] name = "forge-std" diff --git a/cartesi-rollups/contracts/src/DaveAppFactory.sol b/cartesi-rollups/contracts/src/DaveAppFactory.sol index 1486b268..050bdbca 100644 --- a/cartesi-rollups/contracts/src/DaveAppFactory.sol +++ b/cartesi-rollups/contracts/src/DaveAppFactory.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.30; import {Create2} from "@openzeppelin-contracts-5.5.0/utils/Create2.sol"; -import {DataAvailability} from "cartesi-rollups-contracts-3.0.0/src/common/DataAvailability.sol"; import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; import {IOutputsMerkleRootValidator} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; @@ -62,23 +61,14 @@ contract DaveAppFactory is IDaveAppFactory { ); } - /// @notice Encode the data availability blob for applications that only use the input box as DA. - function _encodeInputBoxDataAvailability() internal view returns (bytes memory) { - return abi.encodeCall(DataAvailability.InputBox, (INPUT_BOX)); - } - /// @notice Instantiate a new application contract owned by the current contract, /// with no outputs Merkle root validator (the zero address), and with the input box - /// as the only data availability source. + /// as the data-availability source. function _newApplication(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt) internal returns (IApplication) { - bytes memory dataAvailability = _encodeInputBoxDataAvailability(); - return - APP_FACTORY.newApplication( - NO_VALIDATOR, address(this), templateHash, dataAvailability, withdrawalConfig, salt - ); + return APP_FACTORY.newApplication(NO_VALIDATOR, address(this), templateHash, INPUT_BOX, withdrawalConfig, salt); } /// @notice Instantiate a new `DaveConsensus` contract. @@ -108,9 +98,8 @@ contract DaveAppFactory is IDaveAppFactory { WithdrawalConfig calldata withdrawalConfig, bytes32 salt ) internal view returns (address) { - bytes memory dataAvailability = _encodeInputBoxDataAvailability(); return APP_FACTORY.calculateApplicationAddress( - NO_VALIDATOR, address(this), templateHash, dataAvailability, withdrawalConfig, salt + NO_VALIDATOR, address(this), templateHash, INPUT_BOX, withdrawalConfig, salt ); } diff --git a/cartesi-rollups/contracts/src/DaveConsensus.sol b/cartesi-rollups/contracts/src/DaveConsensus.sol index 2cc086a1..8e325908 100644 --- a/cartesi-rollups/contracts/src/DaveConsensus.sol +++ b/cartesi-rollups/contracts/src/DaveConsensus.sol @@ -405,6 +405,16 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker { return Machine.Hash.unwrap(_lastFinalizedMachineStateHash); } + function wasInputFinalized(address appContract, uint256 inputIndex, uint256) + external + view + override + onlyValidAppContract(appContract) + returns (bool) + { + return inputIndex < _inputIndexLowerBound; + } + function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC165) returns (bool) { return interfaceId == type(IDaveConsensus).interfaceId || interfaceId == type(IDataProvider).interfaceId || interfaceId == type(IOutputsMerkleRootValidator).interfaceId || super.supportsInterface(interfaceId); diff --git a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol index d278d71b..12abe04c 100644 --- a/cartesi-rollups/contracts/test/DaveAppFactory.t.sol +++ b/cartesi-rollups/contracts/test/DaveAppFactory.t.sol @@ -1,25 +1,22 @@ pragma solidity ^0.8.30; -import {Test} from "forge-std-1.9.6/src/Test.sol"; import {Vm} from "forge-std-1.9.6/src/Vm.sol"; import {Ownable} from "@openzeppelin-contracts-5.2.0/access/Ownable.sol"; import {IERC165} from "@openzeppelin-contracts-5.2.0/utils/introspection/IERC165.sol"; -import {DataAvailability} from "cartesi-rollups-contracts-3.0.0/src/common/DataAvailability.sol"; import {WithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/common/WithdrawalConfig.sol"; import {IOutputsMerkleRootValidator} from "cartesi-rollups-contracts-3.0.0/src/consensus/IOutputsMerkleRootValidator.sol"; -import {ApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/ApplicationFactory.sol"; import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplication.sol"; import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationChecker.sol"; import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol"; import {IApplicationFactoryErrors} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactoryErrors.sol"; import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol"; -import {InputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/InputBox.sol"; import {LibBinaryMerkleTree} from "cartesi-rollups-contracts-3.0.0/src/library/LibBinaryMerkleTree.sol"; import {LibBytes} from "cartesi-rollups-contracts-3.0.0/src/library/LibBytes.sol"; import {LibKeccak256} from "cartesi-rollups-contracts-3.0.0/src/library/LibKeccak256.sol"; import {LibWithdrawalConfig} from "cartesi-rollups-contracts-3.0.0/src/library/LibWithdrawalConfig.sol"; +import {RollupsTest} from "cartesi-rollups-contracts-3.0.0/test/util/RollupsTest.sol"; import {EmulatorConstants} from "step/src/EmulatorConstants.sol"; import {Memory} from "step/src/Memory.sol"; @@ -74,7 +71,7 @@ contract SettlementCallbackReceiver { } } -contract DaveAppFactoryTest is Test { +contract DaveAppFactoryTest is RollupsTest { using LibExternalBinaryKeccak256MerkleTree for bytes32[]; using LibWithdrawalConfig for WithdrawalConfig; using LibBytes for bytes; @@ -82,8 +79,6 @@ contract DaveAppFactoryTest is Test { error UnexpectedLogEmitter(Vm.Log log); error UnexpectedLogTopic0(Vm.Log log); - IInputBox _inputBox; - IApplicationFactory _appFactory; IStateTransition _stateTransition; ITournamentFactory _tournamentFactory; IDaveAppFactory _daveAppFactory; @@ -94,15 +89,14 @@ contract DaveAppFactoryTest is Test { uint256 constant STAGING_GAS_CEILING = 500_000; function setUp() external { - _inputBox = new InputBox(); - _appFactory = new ApplicationFactory(); _stateTransition = new CartesiStateTransition(); _tournamentFactory = new MultiLevelTournamentFactory( new Tournament(), new CanonicalTournamentParametersProvider(RESPONSE_BUDGET, MAX_ALLOWANCE), _stateTransition ); - _daveAppFactory = new DaveAppFactory(_inputBox, _appFactory, _tournamentFactory); + _daveAppFactory = + new DaveAppFactory(_contracts.core.inputBox, _contracts.core.applicationFactory, _tournamentFactory); _settlementCallbackReceiver = new SettlementCallbackReceiver(); } @@ -557,6 +551,7 @@ contract DaveAppFactoryTest is Test { assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); assertFalse(daveConsensus.isOutputsMerkleRootValid(address(appContract), outputsMerkleRoot)); + assertFalse(daveConsensus.wasInputFinalized(address(appContract), vm.randomUint(), vm.randomUint())); // Try re-staging tournament result vm.expectRevert(IDaveConsensus.TournamentResultAlreadyStaged.selector); @@ -646,6 +641,7 @@ contract DaveAppFactoryTest is Test { assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), bytes32(0)); assertFalse(daveConsensus.isOutputsMerkleRootValid(address(appContract), outputsMerkleRoot)); + assertFalse(daveConsensus.wasInputFinalized(address(appContract), vm.randomUint(), vm.randomUint())); vm.expectRevert(_encodeApplicationForeclosed(address(appContract))); this.simulateForeclosureAndAcceptance(appContract, daveConsensus, 0); @@ -753,6 +749,9 @@ contract DaveAppFactoryTest is Test { assertEq(daveConsensus.getLastFinalizedMachineMerkleRoot(address(appContract)), machineMerkleRoot); assertTrue(daveConsensus.isOutputsMerkleRootValid(address(appContract), outputsMerkleRoot)); + // No input was finalized yet because the first epoch is always empty + assertFalse(daveConsensus.wasInputFinalized(address(appContract), vm.randomUint(), vm.randomUint())); + // Acceptance advanced the epoch without touching the retired // tournament's balance; recovery is an explicit call afterward. if (!recoverBeforeStaging) { @@ -782,6 +781,107 @@ contract DaveAppFactoryTest is Test { bytes memory input = vm.randomBytes(inputLength); assertEq(daveConsensus.provideMerkleRootOfInput(inputIndexOutOfBounds, input), bytes32(0)); } + + // See `testWasInputFinalized` for the behavior of `wasInputFinalized` + // once epoch #1 (the first non-empty epoch) is finalized as well. + } + + /// @notice Test `wasInputFinalized` across epoch boundaries. + /// @dev Epoch #0 is sealed on construction and is always empty, so accepting + /// its tournament result finalizes no input at all. Only once epoch #1 is + /// finalized do the inputs it spans become finalized. + function testWasInputFinalized( + bytes32 templateHash, + uint64 claimStagingPeriod, + address sentryManager, + address[] calldata sentries, + WithdrawalConfig calldata withdrawalConfig, + bytes32 salt, + bytes32 outputsMerkleRoot, + bytes[] calldata inputPayloadsOfEpoch1, + bytes[] calldata inputPayloadsOfEpoch2 + ) external { + // This test settles two epochs, so it needs twice the block-number slack. + _randomizeBlockNumber(claimStagingPeriod, 2); + + IApplication appContract; + IDaveConsensus daveConsensus; + + vm.assumeNoRevert(); + (appContract, daveConsensus) = _daveAppFactory.newDaveApp( + templateHash, claimStagingPeriod, sentryManager, sentries, withdrawalConfig, salt + ); + + uint256 numOfInputsOfEpoch1 = inputPayloadsOfEpoch1.length; + uint256 numOfInputsOfEpoch2 = inputPayloadsOfEpoch2.length; + + // Epoch #0 was sealed on construction and is empty, so these inputs are spanned by epoch #1 + for (uint256 i; i < numOfInputsOfEpoch1; ++i) { + _addInput(address(appContract), inputPayloadsOfEpoch1[i]); + } + + // Settling epoch #0 seals epoch #1 over inputs [0, numOfInputsOfEpoch1) + _settleCurrentSealedEpoch(daveConsensus, 0, claimStagingPeriod, outputsMerkleRoot); + + { + uint256 val1; + uint256 val2; + uint256 val3; + + (val1, val2, val3,,,,,) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 1); // epochNumber + assertEq(val2, 0); // inputIndexLowerBound + assertEq(val3, numOfInputsOfEpoch1); // inputIndexUpperBound + } + + // Epoch #0 was empty, so no input was finalized by its acceptance + for (uint256 i; i < numOfInputsOfEpoch1; ++i) { + assertFalse(daveConsensus.wasInputFinalized(address(appContract), i, vm.randomUint())); + } + assertFalse(daveConsensus.wasInputFinalized(address(appContract), vm.randomUint(), vm.randomUint())); + + // These inputs are spanned by epoch #2, which is still accumulating + for (uint256 i; i < numOfInputsOfEpoch2; ++i) { + _addInput(address(appContract), inputPayloadsOfEpoch2[i]); + } + + // Settling epoch #1 seals epoch #2 over + // inputs [numOfInputsOfEpoch1, numOfInputsOfEpoch1 + numOfInputsOfEpoch2) + _settleCurrentSealedEpoch(daveConsensus, 1, claimStagingPeriod, outputsMerkleRoot); + + { + uint256 val1; + uint256 val2; + uint256 val3; + + (val1, val2, val3,,,,,) = daveConsensus.getCurrentSealedEpoch(); + + assertEq(val1, 2); // epochNumber + assertEq(val2, numOfInputsOfEpoch1); // inputIndexLowerBound + assertEq(val3, numOfInputsOfEpoch1 + numOfInputsOfEpoch2); // inputIndexUpperBound + } + + // Every input spanned by epoch #1 is now finalized, + // regardless of the block number passed to the function + for (uint256 i; i < numOfInputsOfEpoch1; ++i) { + assertTrue(daveConsensus.wasInputFinalized(address(appContract), i, vm.randomUint())); + } + + // No input spanned by epoch #2 is finalized, because epoch #2 is still accumulating + for (uint256 i = numOfInputsOfEpoch1; i < numOfInputsOfEpoch1 + numOfInputsOfEpoch2; ++i) { + assertFalse(daveConsensus.wasInputFinalized(address(appContract), i, vm.randomUint())); + } + + // Any index at or past the lower bound is reported as not finalized, + // whether or not an input with such an index exists in the input box + uint256 randomInputIndex = vm.randomUint(numOfInputsOfEpoch1, type(uint256).max); + assertFalse(daveConsensus.wasInputFinalized(address(appContract), randomInputIndex, vm.randomUint())); + + // The function still rejects any address other than the application contract + address notAppContract = _randomAddressNotEq(address(appContract)); + vm.expectRevert(_encodeApplicationMismatch(address(appContract), notAppContract)); + daveConsensus.wasInputFinalized(notAppContract, vm.randomUint(), vm.randomUint()); } function testRootFailureCannotBeStaged( @@ -1064,26 +1164,20 @@ contract DaveAppFactoryTest is Test { } else { revert UnexpectedLogTopic0(log); } - } else if (log.emitter == address(_appFactory)) { + } else if (log.emitter == address(_contracts.core.applicationFactory)) { if (log.topics[0] == IApplicationFactory.ApplicationCreated.selector) { ++numOfApplicationCreatedEvents; assertEq(log.topics[1], bytes32(0)); // outputsMerkleRootValidator address arg1; bytes32 arg2; - bytes memory arg3; + address arg3; WithdrawalConfig memory arg4; address arg5; (arg1, arg2, arg3, arg4, arg5) = - abi.decode(log.data, (address, bytes32, bytes, WithdrawalConfig, address)); + abi.decode(log.data, (address, bytes32, address, WithdrawalConfig, address)); assertEq(arg1, address(_daveAppFactory)); // appOwner assertEq(arg2, templateHash); - { - (bool isValid, bytes32 selector, bytes memory args) = arg3.consumeBytes4(); - assertTrue(isValid, "Expected data availability to be valid"); - assertEq(selector, DataAvailability.InputBox.selector); - address inputBoxAddress = abi.decode(args, (address)); - assertEq(inputBoxAddress, address(_inputBox)); - } + assertEq(arg3, address(_contracts.core.inputBox)); assertEq(abi.encode(arg4), abi.encode(withdrawalConfig)); assertEq(arg5, address(appContract)); } else { @@ -1099,7 +1193,7 @@ contract DaveAppFactoryTest is Test { (arg1, arg2, arg3) = abi.decode(log.data, (address, address, address)); - assertEq(arg1, address(_inputBox)); + assertEq(arg1, address(_contracts.core.inputBox)); assertEq(arg2, address(appContract)); assertEq(arg3, address(_tournamentFactory)); } else if (log.topics[0] == IDaveConsensus.EpochSealed.selector) { @@ -1208,7 +1302,7 @@ contract DaveAppFactoryTest is Test { assertEq(val3, 0); // epochNumber } - assertEq(address(daveConsensus.getInputBox()), address(_inputBox)); + assertEq(address(daveConsensus.getInputBox()), address(_contracts.core.inputBox)); assertEq(address(daveConsensus.getApplicationContract()), address(appContract)); assertEq(address(daveConsensus.getTournamentFactory()), address(_tournamentFactory)); assertEq(daveConsensus.getClaimStagingPeriod(), claimStagingPeriod); @@ -1231,6 +1325,9 @@ contract DaveAppFactoryTest is Test { vm.expectRevert(_encodeApplicationMismatch(address(appContract), notAppContract)); daveConsensus.isOutputsMerkleRootValid(notAppContract, bytes32(vm.randomUint())); + vm.expectRevert(_encodeApplicationMismatch(address(appContract), notAppContract)); + daveConsensus.wasInputFinalized(notAppContract, vm.randomUint(), vm.randomUint()); + bytes4 unsupportedInterfaceId; while (true) { @@ -1287,19 +1384,68 @@ contract DaveAppFactoryTest is Test { } function _randomizeBlockNumber(uint64 claimStagingPeriod) internal { + _randomizeBlockNumber(claimStagingPeriod, 1); + } + + function _randomizeBlockNumber(uint64 claimStagingPeriod, uint256 numOfEpochsToSettle) internal { // We limit the block number by type(uint64).max because the PRT contracts // use block numbers for time-keeping, and stores them as uint64 values. // We assume there is some slack so we can fast-forward to a block in which // the tournament is closed, and we can stage the tournament result, and a - // block in which the staged tournament result can be accepted. + // block in which the staged tournament result can be accepted. Tests that + // settle more than one epoch need one such slack per epoch. // We type the claim staging period as uint64 because otherwise the fuzzer // would often pick values too high for these assumptions. uint256 blockNumber = vm.getBlockNumber(); - uint64 maxAllowance = Time.Duration.unwrap(MAX_ALLOWANCE); - vm.assume(blockNumber <= type(uint256).max - maxAllowance); - vm.assume(blockNumber + maxAllowance <= type(uint256).max - claimStagingPeriod); - vm.assume(blockNumber + maxAllowance + claimStagingPeriod <= type(uint64).max); - vm.roll(blockNumber + vm.randomUint(0, type(uint64).max - maxAllowance - claimStagingPeriod)); + uint256 maxAllowance = Time.Duration.unwrap(MAX_ALLOWANCE); + uint256 slack = numOfEpochsToSettle * (maxAllowance + uint256(claimStagingPeriod)); + vm.assume(blockNumber + slack <= type(uint64).max); + vm.roll(vm.randomUint(blockNumber, type(uint64).max - slack)); + } + + /// @notice Join, close, stage and accept the tournament of the current sealed epoch. + /// @dev Assumes that the block number budget reserved by `_randomizeBlockNumber` + /// still has room for one epoch settlement. + function _settleCurrentSealedEpoch( + IDaveConsensus daveConsensus, + uint256 epochNumber, + uint64 claimStagingPeriod, + bytes32 outputsMerkleRoot + ) internal { + ITournament tournament; + (,,, tournament,,,,) = daveConsensus.getCurrentSealedEpoch(); + + bytes32[] memory outputsMerkleRootProof = _randomProof(Memory.LOG2_MAX_SIZE); + bytes32 machineMerkleRoot = outputsMerkleRootProof.merkleRootAfterReplacement( + EmulatorConstants.AR_CMIO_TX_BUFFER_START >> EmulatorConstants.HASH_TREE_LOG2_WORD_SIZE, + keccak256(abi.encode(outputsMerkleRoot)) + ); + + bytes32[] memory finalStateProof = _randomProof(tournament.tournamentArguments().commitmentArgs.height); + (bytes32 leftChild, bytes32 rightChild) = _getCommitmentChildren(machineMerkleRoot, finalStateProof); + + address submitter = vm.randomAddress(); + uint256 bondValue = tournament.bondValue(); + vm.deal(submitter, bondValue); + + vm.prank(submitter); + tournament.joinTournament{value: bondValue}( + Machine.Hash.wrap(machineMerkleRoot), finalStateProof, Tree.Node.wrap(leftChild), Tree.Node.wrap(rightChild) + ); + + // Fast-forward to a block in which the tournament is closed and finished + vm.roll(vm.getBlockNumber() + Time.Duration.unwrap(MAX_ALLOWANCE)); + assertTrue(tournament.isFinished()); + + vm.prank(vm.randomAddress()); + daveConsensus.stageTournamentResult(epochNumber, outputsMerkleRoot, outputsMerkleRootProof); + + // Fast-forward to a block in which the claim staging period is over. Since the + // application has no sentries, this is the only way of accepting the staged result. + vm.roll(vm.getBlockNumber() + claimStagingPeriod); + + vm.prank(vm.randomAddress()); + daveConsensus.acceptStagedTournamentResult(epochNumber); } function _randomProof(uint256 n) internal returns (bytes32[] memory proof) { @@ -1323,11 +1469,11 @@ contract DaveAppFactoryTest is Test { } function _addInput(address appContract, bytes memory payload) internal returns (bytes memory input) { - uint256 index = _inputBox.getNumberOfInputs(appContract); + uint256 index = _contracts.core.inputBox.getNumberOfInputs(appContract); vm.recordLogs(); - _inputBox.addInput(appContract, payload); + _contracts.core.inputBox.addInput(appContract, payload); Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -1335,7 +1481,7 @@ contract DaveAppFactoryTest is Test { Vm.Log memory log = logs[0]; - if (log.emitter == address(_inputBox)) { + if (log.emitter == address(_contracts.core.inputBox)) { if (log.topics[0] == IInputBox.InputAdded.selector) { assertEq(log.topics[1], bytes32(uint256(uint160(appContract)))); assertEq(log.topics[2], bytes32(index)); diff --git a/cartesi-rollups/node/Cargo.toml b/cartesi-rollups/node/Cargo.toml index d51d084f..a971b3ee 100644 --- a/cartesi-rollups/node/Cargo.toml +++ b/cartesi-rollups/node/Cargo.toml @@ -48,7 +48,6 @@ log = { workspace = true } rusqlite = { workspace = true } rusqlite_migration = { workspace = true } hex = { workspace = true } -serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tempfile = "3" env_logger = "0.11.5" diff --git a/cartesi-rollups/node/src/blockchain_reader/test_utils.rs b/cartesi-rollups/node/src/blockchain_reader/test_utils.rs index 59bcd64f..823f8747 100644 --- a/cartesi-rollups/node/src/blockchain_reader/test_utils.rs +++ b/cartesi-rollups/node/src/blockchain_reader/test_utils.rs @@ -14,7 +14,6 @@ use alloy::{ use cartesi_dave_contracts::i_dave_app_factory::IDaveAppFactory::{self, WithdrawalConfig}; use cartesi_machine::{Machine, config::runtime::RuntimeConfig}; use cartesi_rollups_contracts::i_input_box::IInputBox; -use serde::Deserialize; use std::{fs, path::PathBuf}; type Result = std::result::Result>; @@ -23,11 +22,6 @@ const PROGRAM: &str = "../../test/programs/echo/"; const ANVIL_STATE: &str = "../../cartesi-rollups/contracts/state.json"; const DEPLOYMENTS: &str = "../../cartesi-rollups/contracts/deployments/31337"; -#[derive(Deserialize)] -struct Deployment { - address: String, -} - pub fn program_path() -> PathBuf { PathBuf::from(PROGRAM).canonicalize().unwrap() } @@ -58,10 +52,9 @@ pub fn deployments_path() -> PathBuf { } pub fn deployment_address(contract_id: &str) -> Address { - let deployment_path = deployments_path().join(format!("{}.json", contract_id)); - let deployment_json = fs::read_to_string(deployment_path).unwrap(); - let deployment: Deployment = serde_json::from_str(&deployment_json).unwrap(); - Address::from_hex(deployment.address).unwrap() + let deployment_path = deployments_path().join(format!("{}.txt", contract_id)); + let deployment = fs::read_to_string(deployment_path).unwrap(); + Address::from_hex(deployment).unwrap() } /// A per-request timeout on every test provider: a wedged anvil must diff --git a/prt/contracts/script/BaseDeploymentScript.sol b/prt/contracts/script/BaseDeploymentScript.sol index ba7c9dcc..92dc27c7 100644 --- a/prt/contracts/script/BaseDeploymentScript.sol +++ b/prt/contracts/script/BaseDeploymentScript.sol @@ -9,11 +9,23 @@ import {Script} from "forge-std-1.9.6/src/Script.sol"; import {Vm} from "forge-std-1.9.6/src/Vm.sol"; /// @notice A base contract for deployment scripts. -/// @dev Deployments are serialized to JSON files containing the -/// contract name and address, and stored in chain-specific directories -/// per project. They can be stored (requires read-write fs permission) -/// and loaded from other projects (requires read fs permission). +/// @dev Deployments are serialized to files named after the contract and +/// stored in chain-specific directories per project. Every deployment is +/// written in two formats: a plaintext (TXT) file containing only the +/// address, and a JSON file containing the address and the contract name. +/// The TXT format is the canonical one, and the only one read back by +/// `_loadDeployment`. The JSON format is deprecated and kept only so that +/// clients still reading it can migrate at their own pace; it will be +/// removed once they have. They can be stored (requires read-write fs +/// permission) and loaded from other projects (requires read fs permission). abstract contract BaseDeploymentScript is Script { + /// @notice The extension of plaintext deployment files. + string constant TXT_EXTENSION = ".txt"; + + /// @notice The extension of JSON deployment files. + /// @dev Deprecated. See the contract-level documentation. + string constant JSON_EXTENSION = ".json"; + /// @notice The set of deployed contract names of the current project. mapping(string => bool) private _wasContractDeployed; @@ -54,7 +66,8 @@ abstract contract BaseDeploymentScript is Script { /// @notice Store a deployment in the current project. /// @param contractName The contract name /// @param deployment The deployment address - /// @return depoyment The deployment address + /// @return deployment The deployment address + /// @dev Writes the deployment in both the TXT and the JSON formats. function _storeDeployment(string memory contractName, address deployment) internal returns (address) @@ -76,50 +89,129 @@ abstract contract BaseDeploymentScript is Script { _wasContractDeployed[contractName] = true; _deploymentByContractName[contractName] = deployment; - string memory deploymentStr = vm.toString(deployment); - string memory objectKey = - string.concat(contractName, "@", deploymentStr); - string memory json; - json = vmSafe.serializeAddress(objectKey, "address", deployment); - json = vmSafe.serializeString(objectKey, "contractName", contractName); string memory dir = _getCurrentChainDeploymentsDir("."); vm.createDir(dir, true); - string memory path = _getDeploymentFilePath(dir, contractName); - vmSafe.writeJson(json, path); + _writeTxtDeployment(dir, contractName, deployment); + _writeJsonDeployment(dir, contractName, deployment); return deployment; } + /// @notice Write a deployment to a TXT file, which holds the + /// deployment address and nothing else. + /// @param dir The deployment directory + /// @param contractName The contract name + /// @param deployment The deployment address + function _writeTxtDeployment( + string memory dir, + string memory contractName, + address deployment + ) private { + /// forge-lint: disable-next-line(unsafe-cheatcode) + vmSafe.writeFile( + _getDeploymentFilePath(dir, contractName, TXT_EXTENSION), + vmSafe.toString(deployment) + ); + } + + /// @notice Write a deployment to a JSON file, which holds the + /// deployment address along with the contract name. + /// @param dir The deployment directory + /// @param contractName The contract name + /// @param deployment The deployment address + /// @dev Deprecated. See the contract-level documentation. + function _writeJsonDeployment( + string memory dir, + string memory contractName, + address deployment + ) private { + string memory objectKey = string.concat( + contractName, "@", vmSafe.toString(deployment) + ); + string memory json; + json = vmSafe.serializeAddress(objectKey, "address", deployment); + json = vmSafe.serializeString(objectKey, "contractName", contractName); + /// forge-lint: disable-next-line(unsafe-cheatcode) + vmSafe.writeFile( + _getDeploymentFilePath(dir, contractName, JSON_EXTENSION), json + ); + } + /// @notice Load a deployment from a project. /// @param projectRoot The project root path /// @param contractName The contract name /// @return deployment The deployment address + /// @dev Reads the TXT file. The JSON file is deprecated and, even + /// though it is still written, it is no longer read back. function _loadDeployment( string memory projectRoot, string memory contractName ) internal view returns (address deployment) { string memory dir = _getCurrentChainDeploymentsDir(projectRoot); - string memory path = _getDeploymentFilePath(dir, contractName); + return _readTxtDeployment( + _getDeploymentFilePath(dir, contractName, TXT_EXTENSION) + ); + } + + /// @notice Read a deployment address from a TXT file. + /// @param path The TXT file path + /// @return deployment The deployment address + /// @dev The file is expected to hold the address and nothing else, + /// with no trailing newline, as written by `_writeTxtDeployment`. + function _readTxtDeployment(string memory path) + private + view + returns (address deployment) + { + /// forge-lint: disable-next-line(unsafe-cheatcode) + return vmSafe.parseAddress(vmSafe.readFile(path)); + } + + /// @notice Read a deployment address from a JSON file. + /// @param path The JSON file path + /// @return contractName The contract name + /// @return deployment The deployment address + /// @dev Deprecated. See the contract-level documentation. + function _readJsonDeployment(string memory path) + private + view + returns (string memory contractName, address deployment) + { /// forge-lint: disable-next-line(unsafe-cheatcode) string memory json = vmSafe.readFile(path); - return vmSafe.parseJsonAddress(json, ".address"); + contractName = vmSafe.parseJsonString(json, ".contractName"); + deployment = vmSafe.parseJsonAddress(json, ".address"); } /// @notice Import all deployments from a project. /// @param projectRoot The project root path /// @dev The traversal of the deployments directory is shallow (maxDepth = 1). /// Symbolic links are not followed to avoid unbounded recursion. + /// Both TXT and JSON files are imported, since a project may have + /// migrated to the TXT format already, or may not have yet. A contract + /// deployed in both formats is therefore imported twice; the second + /// import is a no-op if both files agree, and raises a + /// `ContractNameConflict` error if they do not. Files with any other + /// extension are not deployment artifacts, and are skipped. function _importDeployments(string memory projectRoot) internal { string memory dir = _getCurrentChainDeploymentsDir(projectRoot); Vm.DirEntry[] memory dirEntries = vm.readDir(dir, 1, false); for (uint256 i; i < dirEntries.length; ++i) { Vm.DirEntry memory dirEntry = dirEntries[i]; - if (vm.isFile(dirEntry.path)) { - /// forge-lint: disable-next-line(unsafe-cheatcode) - string memory json = vmSafe.readFile(dirEntry.path); + if (!vm.isFile(dirEntry.path)) { + continue; + } + if (_hasSuffix(dirEntry.path, TXT_EXTENSION)) { + // A TXT file holds no contract name, + // so it is taken from the file name instead. + string memory contractName = + _getContractNameFromFilePath(dirEntry.path, TXT_EXTENSION); _storeDeployment( - vmSafe.parseJsonString(json, ".contractName"), - vmSafe.parseJsonAddress(json, ".address") + contractName, _readTxtDeployment(dirEntry.path) ); + } else if (_hasSuffix(dirEntry.path, JSON_EXTENSION)) { + (string memory contractName, address deployment) = + _readJsonDeployment(dirEntry.path); + _storeDeployment(contractName, deployment); } } } @@ -137,15 +229,74 @@ abstract contract BaseDeploymentScript is Script { ); } - /// @notice Get the path of a deployment file given the directory and contract name. + /// @notice Get the path of a deployment file given the directory, + /// the contract name and the file extension. /// @param dir The deployment directory (see `_getCurrentChainDeploymentsDir`) /// @param contractName The contract name + /// @param extension The file extension, including the leading dot /// @return path The deployment file path function _getDeploymentFilePath( string memory dir, - string memory contractName + string memory contractName, + string memory extension ) internal pure returns (string memory path) { - path = string.concat(dir, "/", contractName, ".json"); + path = string.concat(dir, "/", contractName, extension); + } + + /// @notice Get the contract name a deployment file is named after. + /// @param path The deployment file path + /// @param extension The file extension, including the leading dot + /// @return contractName The contract name + /// @dev Assumes `path` ends with `extension` (see `_hasSuffix`). + /// The result is not validated here: `_storeDeployment` rejects it + /// through `_isContractNameValid` if the file was named arbitrarily. + function _getContractNameFromFilePath( + string memory path, + string memory extension + ) internal pure returns (string memory contractName) { + bytes memory pathBytes = bytes(path); + uint256 end = pathBytes.length - bytes(extension).length; + + // The name starts right after the last path separator, if any. + uint256 start; + for (uint256 i = end; i > 0; --i) { + if (pathBytes[i - 1] == "/") { + start = i; + break; + } + } + + bytes memory nameBytes = new bytes(end - start); + for (uint256 i; i < nameBytes.length; ++i) { + nameBytes[i] = pathBytes[start + i]; + } + contractName = string(nameBytes); + } + + /// @notice Check whether a string ends with a given suffix. + /// @param str The string + /// @param suffix The suffix + /// @return True if `str` ends with `suffix`, false otherwise + function _hasSuffix(string memory str, string memory suffix) + internal + pure + returns (bool) + { + bytes memory strBytes = bytes(str); + bytes memory suffixBytes = bytes(suffix); + + if (strBytes.length < suffixBytes.length) { + return false; + } + + uint256 offset = strBytes.length - suffixBytes.length; + for (uint256 i; i < suffixBytes.length; ++i) { + if (strBytes[offset + i] != suffixBytes[i]) { + return false; + } + } + + return true; } /// @notice Checks if a contract name is valid according to Solidity naming rules @@ -166,23 +317,18 @@ abstract contract BaseDeploymentScript is Script { // Check first character: must be a-z, A-Z, _, or $ bytes1 firstChar = nameBytes[0]; - if (!((firstChar >= 0x41 && firstChar <= 0x5A) // A-Z - || (firstChar >= 0x61 && firstChar <= 0x7A) // a-z - || firstChar == 0x5F // _ - || firstChar == 0x24 // $ - )) { + if (!((firstChar >= "A" && firstChar <= "Z") + || (firstChar >= "a" && firstChar <= "z") + || firstChar == "_" || firstChar == "$")) { return false; } // Check remaining characters: must be a-z, A-Z, 0-9, _, or $ for (uint256 i = 1; i < nameBytes.length; i++) { bytes1 char = nameBytes[i]; - if (!((char >= 0x41 && char <= 0x5A) // A-Z - || (char >= 0x61 && char <= 0x7A) // a-z - || (char >= 0x30 && char <= 0x39) // 0-9 - || char == 0x5F // _ - || char == 0x24 // $ - )) { + if (!((char >= "A" && char <= "Z") || (char >= "a" && char <= "z") + || (char >= "0" && char <= "9") || char == "_" + || char == "$")) { return false; } } diff --git a/prt/contracts/script/Deployment.s.sol b/prt/contracts/script/Deployment.s.sol index 4374ec34..745d2046 100644 --- a/prt/contracts/script/Deployment.s.sol +++ b/prt/contracts/script/Deployment.s.sol @@ -131,7 +131,9 @@ contract DeploymentScript is BaseDeploymentScript { error UnregisteredChainKind(ChainKind chainKind); /// @notice Deploy the PRT contracts. - /// @dev Serializes deployed contract addresses to `deployments.json`. + /// @dev Serializes deployed contract addresses to both + /// `deployments//.txt` and + /// `deployments//.json` (deprecated). function run() external { _registerChains(); _registerChainKinds(); diff --git a/script/devnet-fingerprint.sh b/script/devnet-fingerprint.sh index a3e3b691..e511d950 100755 --- a/script/devnet-fingerprint.sh +++ b/script/devnet-fingerprint.sh @@ -17,7 +17,7 @@ CDPATH= cd -- "$repo_root" || { exit 2 } -readonly base_contracts="cartesi-rollups/contracts/dependencies/cartesi-rollups-contracts-3.0.0-alpha.6" +readonly base_contracts="cartesi-rollups/contracts/dependencies/cartesi-rollups-contracts-3.0.0-alpha.9" readonly input_format="devnet-inputs-v5" readonly manifest_format="v4" diff --git a/script/machine-image-fingerprint.sh b/script/machine-image-fingerprint.sh index 9e719126..97557537 100755 --- a/script/machine-image-fingerprint.sh +++ b/script/machine-image-fingerprint.sh @@ -51,17 +51,13 @@ producer_script() { deployment_address() { local contract_name=$1 - local deployment="cartesi-rollups/contracts/deployments/31337/${contract_name}.json" + local deployment="cartesi-rollups/contracts/deployments/31337/${contract_name}.txt" if [ ! -f "$deployment" ]; then echo "error: missing devnet deployment: $deployment" >&2 return 1 fi - jq -er ' - .address - | select(type == "string") - | select(test("^0x[0-9A-Fa-f]{40}$")) - ' "$deployment" + cat "$deployment" } inputs_digest() { @@ -132,7 +128,7 @@ inputs_digest() { fi generator_hash=$(sha256_file \ test/programs/honeypot/generate-devnet-honeypot-config.sh) || return $? - portal_address=$(deployment_address ERC20Portal) || return 1 + portal_address=$(deployment_address Erc20Portal) || return 1 token_address=$(deployment_address TestFungibleToken) || return 1 else for input in linux.bin rootfs.ext2; do diff --git a/script/tests/devnet-fingerprint.sh b/script/tests/devnet-fingerprint.sh index 234bcea6..b429201e 100755 --- a/script/tests/devnet-fingerprint.sh +++ b/script/tests/devnet-fingerprint.sh @@ -27,7 +27,7 @@ source_roots=( config_roots=( prt/contracts cartesi-rollups/contracts - cartesi-rollups/contracts/dependencies/cartesi-rollups-contracts-3.0.0-alpha.6 + cartesi-rollups/contracts/dependencies/cartesi-rollups-contracts-3.0.0-alpha.9 ) for root in "${source_roots[@]}"; do @@ -54,6 +54,8 @@ printf '#!/usr/bin/env bash\n' \ mkdir -p "${fixture}/cartesi-rollups/contracts/deployments/31337" printf '{"state":"ready"}\n' \ >"${fixture}/cartesi-rollups/contracts/state.json" +printf '0x1' \ + >"${fixture}/cartesi-rollups/contracts/deployments/31337/Contract.txt" printf '{"address":"0x1"}\n' \ >"${fixture}/cartesi-rollups/contracts/deployments/31337/Contract.json" @@ -171,9 +173,13 @@ expect_status 1 verify "${fixture}/cartesi-rollups/contracts" printf '{"state":"ready"}\n' \ >"${fixture}/cartesi-rollups/contracts/state.json" +printf '0x2' \ + >"${fixture}/cartesi-rollups/contracts/deployments/31337/Contract.txt" printf '{"address":"0x2"}\n' \ >"${fixture}/cartesi-rollups/contracts/deployments/31337/Contract.json" expect_status 1 verify "${fixture}/cartesi-rollups/contracts" +printf '0x1' \ + >"${fixture}/cartesi-rollups/contracts/deployments/31337/Contract.txt" printf '{"address":"0x1"}\n' \ >"${fixture}/cartesi-rollups/contracts/deployments/31337/Contract.json" diff --git a/test/e2e/rollups/justfile b/test/e2e/rollups/justfile index 35b02914..e3a5a94e 100644 --- a/test/e2e/rollups/justfile +++ b/test/e2e/rollups/justfile @@ -32,10 +32,10 @@ test PROGRAM SCENARIO: (preflight PROGRAM SCENARIO) ANVIL_LOAD_PATH=`realpath {{ANVIL_LOAD_PATH}}` \ TEMPLATE_MACHINE=`realpath "../../../test/programs/$1/machine-image"` \ TEMPLATE_MACHINE_HASH=`cartesi-machine-stored-hash "../../../test/programs/$1/machine-image"` \ - DAVE_APP_FACTORY=`jq -r .address {{DEPLOYMENTS_DIR}}/DaveAppFactory.json` \ - INPUT_BOX=`jq -r .address {{DEPLOYMENTS_DIR}}/InputBox.json` \ - ERC20_PORTAL=`jq -r .address {{DEPLOYMENTS_DIR}}/ERC20Portal.json` \ - ERC20_TOKEN=`jq -r .address {{DEPLOYMENTS_DIR}}/TestFungibleToken.json` \ + DAVE_APP_FACTORY=`cat {{DEPLOYMENTS_DIR}}/DaveAppFactory.txt` \ + INPUT_BOX=`cat {{DEPLOYMENTS_DIR}}/InputBox.txt` \ + ERC20_PORTAL=`cat {{DEPLOYMENTS_DIR}}/Erc20Portal.txt` \ + ERC20_TOKEN=`cat {{DEPLOYMENTS_DIR}}/TestFungibleToken.txt` \ lua5.4 "scenarios/$2.lua" # run PRT rollups echo test diff --git a/test/e2e/rollups/scenarios/deposit_withdrawal.lua b/test/e2e/rollups/scenarios/deposit_withdrawal.lua index 87138676..695fcb5b 100755 --- a/test/e2e/rollups/scenarios/deposit_withdrawal.lua +++ b/test/e2e/rollups/scenarios/deposit_withdrawal.lua @@ -77,7 +77,7 @@ assert( -- (This makes the portal send an input to the app) env.sender:_send_tx( ERC20_PORTAL_ADDRESS, - "depositERC20Tokens(address,address,uint256,bytes)", + "depositErc20Tokens(address,address,uint256,bytes)", {ERC20_TOKEN_ADDRESS, env.app_address, ERC20_AMOUNT, "0x"} ) diff --git a/test/programs/honeypot/generate-devnet-honeypot-config.sh b/test/programs/honeypot/generate-devnet-honeypot-config.sh index dc4b27f2..90118605 100755 --- a/test/programs/honeypot/generate-devnet-honeypot-config.sh +++ b/test/programs/honeypot/generate-devnet-honeypot-config.sh @@ -18,10 +18,10 @@ get_devnet_deployment_address() { [[ "$contract_name" =~ ^[a-zA-Z_$][a-zA-Z0-9_$]*$ ]] || (>&2 echo "$contract_name is not a valid contract name." && exit 1) - jq -r .address "cartesi-rollups/contracts/deployments/31337/${contract_name}.json" + cat "cartesi-rollups/contracts/deployments/31337/${contract_name}.txt" } -ERC20_PORTAL_ADDRESS=$(get_devnet_deployment_address ERC20Portal) +ERC20_PORTAL_ADDRESS=$(get_devnet_deployment_address Erc20Portal) ERC20_WITHDRAWAL_ADDRESS='0x70997970C51812dc3A010C7d01b50e0d17dc79C8' ERC20_TOKEN_ADDRESS=$(get_devnet_deployment_address TestFungibleToken)