This is my wishlist for Cartesi Rollups contracts v3. ## <a id="merkelize-inputs"></a>Merkelize Inputs Currently, the `InputBox` hashes every input it receives and `DaveConsensus` computes the Merkle root of an input only if necessary during a dispute. It is cheaper to compute the `keccak256` of an input than its Merkle root, so we added a hard limit on the input size to ensure that every input sent to the `InputBox` contract can be merkelized later. What we want to do is, instead, make `InputBox` merkelize every input it receives. This does mean that adding inputs (and there depositing assets) will be more expensive, but this might be ammortized on a later release once we increase the data block size from 32 bytes to, say, 128 bytes, or even 4 kilobytes. We might also change the arity of the tree, which is currently binary, to be 4-ary, for example. There are also ZK approaches. For now, we will compute the root of a binary Merkle tree with 32-byte data blocks. With this, the `InputBox` will no longer serve `keccak256` hashes but rather only Merkle roots. The `getInputHash` function will be renamed as `getInputMerkleRoot` to make this breaking change clear. This will make the `DaveConsensus` contract much simpler. ## <a id="store-number-of-inputs-before-current-block"></a>Store Number of Inputs Before Current Block If we use block numbers for epoch boundaries, we need to add a function in the `IInputBox` interface that allows `DaveConsensus` to get the number of inputs an application has received before the current block. ## <a id="raise-input-size-limit"></a>Raise Input Size Limit Previously, we lowered the input size limit to 64 KB to ensure that inputs can be merkelized later by the `DaveConsensus` contract. [If inputs are merkelized upon addition](#merkelize-inputs), then we can raise this input limit back to the machine's limit of 2 MB. ## <a id="remove-nondeterministic-deployment-function-from-factories"></a>Remove nonce-based deployment functions Some of our factories contain two deployment entrypoints: nondeterministic (nonce-based `CREATE`) and deterministic (salt-based `CREATE2`). The latter entrypoint has an interesting property: you can calculate the address of a contract before even deploying. Althought the first entrypoint has a 100% deployment guarantee, the second entrypoint has a near-100% guarantee if you use a random salt. By removing the nonce-based deployment function, we reduce the size of the factory contracts and avoid users from picking the wrong entrypoint accidentally. ## <a id="remove-calculate-functions-from-factories"></a>Remove address calculation functions Our factories contain a deterministic deployment function and a deterministic address calculation function. However, we don't really need the second function if it is being called from the outside. Users can simply `eth_call` the deterministic deployment function. It even raises an error if the contract has been deployed already. By removing the deterministic address calculation function, we reduce the size of the factory contracts and remove the dependency with OpenZeppelin `Create2` library. ## <a id="register-contracts-upon-deployment"></a>Register contracts upon deployment Once a contract is deployed, the factory could register the contract in an internal set of deployed contracts. This mapping would be served through a view function named something like `wasContractDeployed(address)(bool)`. This function, in turn, would be consulted, for example, by UIs that want to check whether a particular contract was deployed by the trusted factory or not without having to listen to all deployment events since the deployment of the factory itself. ## <a id="ensure-application-contracts-are-deployed"></a>Make `InputBox` check application deployment Currently, the `InputBox` contract doesn't care whether the application you're sending an input to has been deployed or is an EOA. It types `appContract` as just `address`. This could lead to undesired consequences, such as sending inputs or even depositing assets to applications in previous SDK versions, or to not-yet deployed applications, or to nonexisting applications. If the address points to an EOA or was mistyped, then the assets could be locked forever. By making the `InputBox` check whether the application has been deployed by the appointed application factory, then we ensure that the application deployment block number is the first block number you should start listening inputs from, and that deposits are sent to existing applications. This also ensures the front-ends such as the Rollups Explorer don't have to deal with applications with inputs from different `InputBox` contracts (starting from v3). ## <a id="kill-authority"></a>Kill Authority `Authority` is equivalent to a single-validator `Quorum`, which means we could safely remove it from our codebase without sacrificing functionality. However, this would make single-validator actions more expensive due to the increased complexity of `Quorum`. ## <a id="add-dave-consensus"></a>Add PRT Contracts Cartesi Rollups is the main client of PRT. By moving the PRT contracts to the `rollups-contracts` repo, we ensure that integration between them progresses at full speed. ## <a id="rename-dave-consensus"></a>Rename DaveConsensus Admittedly, `DaveConsensus` is not a great name. The fraud-proof algorithm currently live is Permissionless Refereed Tournaments, or PRT for short. It uses a micro-architecture (or `uarch` for short) as step function but could use ZK instead to prove multiple RISC-V steps. Dave is an evolution on PRT with better liveness properties, and requires ZK. ## <a id="share-dave-consensus"></a>Make DaveConsensus Shared The current version of `DaveConsensus` validates a single application. This means that every application that wants to use PRT must instantiate a `DaveConsensus` contract. This was mostly a design decision focused on simplicity. However, by making `DaveConsensus` a shared singleton, much like the `InputBox` contract, we would make it simpler and cheaper for apps to use PRT. This change would also allow us to [remove the application owner role](#rm-app-owner), as it is currently only necessary to solve the chicken-and-egg problem caused by `DaveConsensus` being single-app. This change would also allow us to remove... - the `appContract`, `inputBox` and `initialMachineStateHash` constructor parameters - the `appContract` and `inputBox` parameters from the `ConsensusCreated` event - the `getApplicationContract` function - the `ApplicationMismatch` error Meanwhile, we would have to add... - a `sealEpoch` function - an `appContract` parameter to most functions, events, and errors We would also need to instantiate `IDataProvider` contracts for each PRT dispute. For cheaper deployments, we can leverage [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy contracts. ## <a id="make-outputs-merkle-root-validator-immutable"></a> Make the Outputs Merkle Root Validator Immutable The owner of an application currently has the power to change the outputs Merkle root validator of the application it owns at any time. This means that users need to trust application owners... So, why does this role exist in the first place? The application owner role is currently needed to solve the chicken-and-egg problem with consensus modules that take the application contract's address as constructor argument. So, if we [made `DaveConsensus` a shared contract](#share-dave-consensus), then dynamically changing the outputs Merkle root validator of an application would not be necessary. This change would allow us to remove... - the `OutputsMerkleRootValidatorChanged` event - the `migrateToOutputsMerkleRootValidator` function > [!Note] 🐔🥚 The chicken-and-egg problem > Currently, we use the [`CREATE2` opcode](https://www.evm.codes/?fork=cancun#f5) to deterministically deploy contracts. This opcode computes the address of a contract from the hash of the contract's creation code (which includes the constructor arguments), the address of the deploying contract, and a salt. So, if two contracts receive each other's addresses as constructor arguments, you essentially have an dependency cycle between the two, since it is unfeasible to forge a collision for `keccak256`. That is the case of the `DaveConsensus` and `Application` contracts. And even if you used the [`CREATE` opcode](https://www.evm.codes/?fork=cancun#f0), which uses the factory contract's nonce instead, you would be succeptible to users front-running you. If we made the `DaveConsensus` not receive the application contract address as constructor parameter, by making it shared, we would essentially break the dependency cycle. ## <a id="rm-app-owner"></a>Remove the Application Owner Role The only power that the owner of an application has is to change the outputs Merkle root validator of the application. If we [made the outputs Merkle root validator immutable](#make-outputs-merkle-root-validator-immutable), this role would not be necessary anymore. This would allow us to remove... - the `owner` function - the `transferOwnership` function - the `renounceOwnership` function - the `OwnershipTransferred` event ## Remove the IOwnable interface This interface contains the functions from the `Ownable` abstract contract from the OpenZeppelin smart contract library. It is currently used by `Application` and `Authority`. However, if we [killed Authority](#kill-authority) and [removed the application owner role](#rm-app-owner), then this interface would not be necessary anymore. This would allow us to remove... - the `IOwnable` interface ## Make the Application Factory a Clone Factory [Clones](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Clones) are [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxies implemented by OpenZeppelin in Solidity. They simply forward every message calls to a fixed implementation contract. Due to their reduced size, they can drastically lower deployment costs. Making the `ApplicationFactory` a clone factory would certainly reduce the cost of deploying application contracts, which, in turn, would lower the entrance barrier for application developers that want to deploy their applications to Mainnets. This, in turn, could indirectly help with adoption. In order to do so, there has to be some minor refactoring on the `Application` contract related to immutable variables. In Solidity, when you mark a state variable as `immutable`, it basically embeds this value in the contract's bytecode. However, the `Application` contract will be the implementation contract, and we don't want every `Application` clone to share the same immutable variable. To circumvent this, we embed these immutable variables in the clone's bytecode through some clever low-level assembly (already implemented by the OpenZeppelin developers in the [Clones](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Clones) library; see the `fetchCloneArgs`, `cloneWithImmutableArgs` and `cloneDeterministicWithImmutableArgs` functions).