diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 07fe1817..1ec4e572 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -23,7 +23,7 @@ runs: - name: Set Foundry version id: set-foundry-version shell: bash - run: echo "foundry-version=v1.1.0" >> $GITHUB_OUTPUT + run: echo "foundry-version=v1.2.1" >> $GITHUB_OUTPUT - uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # 1.3.1 with: diff --git a/.github/workflows/upload-rust-bindings.yml b/.github/workflows/upload-rust-bindings.yml index df432ffc..b95221f9 100644 --- a/.github/workflows/upload-rust-bindings.yml +++ b/.github/workflows/upload-rust-bindings.yml @@ -26,6 +26,7 @@ jobs: --crate-version "$VERSION" \ --crate-license "Apache-2.0" \ --crate-description "Rust bindings for Cartesi Rollups smart contracts" \ + --skip-extra-derives \ --alloy-version "0.12.4" env: VERSION: ${{ steps.extract_version.outputs.version }} diff --git a/.gitignore b/.gitignore index d7ac440b..7415ac26 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /coverage /dependencies /deployments -/docs +/forge-docs /node_modules /out lcov.info diff --git a/README.md b/README.md index e02b24ad..ce7fe45d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ deposit assets, submit claims, execute asset withdrawal orders, and more. First, please ensure the following dependencies are installed: - [corepack] -- [foundry] 1.1.0 +- [foundry] 1.2.1 Then, you may clone the repository... diff --git a/cannonfile.toml b/cannonfile.toml index ab24f6a3..836a9ee0 100644 --- a/cannonfile.toml +++ b/cannonfile.toml @@ -55,8 +55,15 @@ create2 = true salt = "0x0000000000000000000000000000000000000000d5556a1a1397890355d302fb" ifExists = "continue" +[deploy.Application] +artifact = "Application" +create2 = true +salt = "<%= zeroHash %>" +ifExists = "continue" + [deploy.ApplicationFactory] artifact = "ApplicationFactory" +args = ["<%= contracts.Application.address %>"] create2 = true salt = "0x0000000000000000000000000000000000000000ba0ce3b081904f01ab19671c" ifExists = "continue" diff --git a/package.json b/package.json index 3b9d3322..d73cd697 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@changesets/cli": "^2.29.4", - "@usecannon/cli": "^2.22.0" + "@usecannon/cli": "^2.23.0" }, "files": [ "out" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 217207a9..6b2124d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^2.29.4 version: 2.29.4 '@usecannon/cli': - specifier: ^2.22.0 - version: 2.22.0(typescript@5.4.5) + specifier: ^2.23.0 + version: 2.23.0(typescript@5.4.5) packages: @@ -31,8 +31,8 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.27.1': - resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} + '@babel/runtime@7.27.3': + resolution: {integrity: sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw==} engines: {node: '>=6.9.0'} '@changesets/apply-release-plan@7.0.12': @@ -163,12 +163,16 @@ packages: '@multiformats/base-x@4.0.1': resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==} - '@noble/curves@1.8.2': - resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.7.2': - resolution: {integrity: sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==} + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': @@ -216,11 +220,11 @@ packages: '@scure/base@1.2.5': resolution: {integrity: sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==} - '@scure/bip32@1.6.2': - resolution: {integrity: sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==} + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - '@scure/bip39@1.5.4': - resolution: {integrity: sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==} + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} @@ -231,18 +235,18 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@22.15.19': - resolution: {integrity: sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw==} + '@types/node@22.15.23': + resolution: {integrity: sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@usecannon/builder@2.22.0': - resolution: {integrity: sha512-kyYrXU44jXeZ6R6/wV9acpF4rw+6/i9PEHnQ1qbLNxPXlFQfM7Ds16OnRP9Mu33lF2izRaCJUuteM0anUfXFAQ==} + '@usecannon/builder@2.23.0': + resolution: {integrity: sha512-a0WwHNM+RYvs4DTnKTagWsPLB/XVySeTFs/DofoJAVhKN2cKSyhhr9i41KcsPHN1pVt3+49VvRhZJm1eyAIq4g==} engines: {node: '>=16.0.0'} - '@usecannon/cli@2.22.0': - resolution: {integrity: sha512-jVwiR85TMRsmjcb9j1cEGQUQ6N+swBvZXGhqOAJy8+VbZotTdjpD+jY6+wl/oootapQlxDXupBnxGlVQDJnuUQ==} + '@usecannon/cli@2.23.0': + resolution: {integrity: sha512-XP+GP4zi8kxJOifqFg1JijbEjvTMH9KbtBRWrOcFkEuqJ3M4TO++vkkkEMue36loMnAaThV2RAxm4j21TjSggg==} hasBin: true '@usecannon/router@4.1.3': @@ -734,8 +738,8 @@ packages: it-parallel-batch@1.0.11: resolution: {integrity: sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==} - jackspeak@4.1.0: - resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} js-sha3@0.8.0: @@ -917,8 +921,8 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - ox@0.6.9: - resolution: {integrity: sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==} + ox@0.7.1: + resolution: {integrity: sha512-+k9fY9PRNuAMHRFIUbiK9Nt5seYHHzSQs9Bj+iMETcGtlpS7SmBzcGSVUQO3+nqGLEiNK4598pHNFlVRaZbRsg==} peerDependencies: typescript: '>=5.4.0' peerDependenciesMeta: @@ -1250,8 +1254,8 @@ packages: varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} - viem@2.30.0: - resolution: {integrity: sha512-hvO4l5JIOnYPL8imULoFQiVTSkebIqzGHmIfsdMfIHpAgBaCx8rJJH9cXAxQeWCqsFuTmjEj1cX912N7HSCgpQ==} + viem@2.30.3: + resolution: {integrity: sha512-RbHiGmOz8ub5VcOPSstslu//hO8Qc2jhBgFLSuYAqEha7Z4OvlsgodBGUsiv2A0QXeIw3KwaK9o6TEujqP5fMA==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -1277,8 +1281,8 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1316,8 +1320,8 @@ packages: peerDependencies: zod: ^3.13.2 - zod@3.25.7: - resolution: {integrity: sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==} + zod@3.25.32: + resolution: {integrity: sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==} snapshots: @@ -1333,7 +1337,7 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} - '@babel/runtime@7.27.1': {} + '@babel/runtime@7.27.3': {} '@changesets/apply-release-plan@7.0.12': dependencies: @@ -1615,14 +1619,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.27.1 + '@babel/runtime': 7.27.3 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.27.1 + '@babel/runtime': 7.27.3 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -1631,11 +1635,13 @@ snapshots: '@multiformats/base-x@4.0.1': {} - '@noble/curves@1.8.2': + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.1': dependencies: - '@noble/hashes': 1.7.2 + '@noble/hashes': 1.8.0 - '@noble/hashes@1.7.2': {} + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -1674,15 +1680,15 @@ snapshots: '@scure/base@1.2.5': {} - '@scure/bip32@1.6.2': + '@scure/bip32@1.7.0': dependencies: - '@noble/curves': 1.8.2 - '@noble/hashes': 1.7.2 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 '@scure/base': 1.2.5 - '@scure/bip39@1.5.4': + '@scure/bip39@1.6.0': dependencies: - '@noble/hashes': 1.7.2 + '@noble/hashes': 1.8.0 '@scure/base': 1.2.5 '@types/long@4.0.2': {} @@ -1691,13 +1697,13 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@22.15.19': + '@types/node@22.15.23': dependencies: undici-types: 6.21.0 '@types/normalize-package-data@2.4.4': {} - '@usecannon/builder@2.22.0(typescript@5.4.5)': + '@usecannon/builder@2.23.0(typescript@5.4.5)': dependencies: '@usecannon/router': 4.1.3 '@usecannon/web-solc': 0.5.1 @@ -1716,8 +1722,8 @@ snapshots: rfdc: 1.4.1 ses: 1.12.0 typestub-ipfs-only-hash: 4.0.0 - viem: 2.30.0(typescript@5.4.5)(zod@3.25.7) - zod: 3.25.7 + viem: 2.30.3(typescript@5.4.5)(zod@3.25.32) + zod: 3.25.32 transitivePeerDependencies: - bufferutil - encoding @@ -1725,11 +1731,11 @@ snapshots: - typescript - utf-8-validate - '@usecannon/cli@2.22.0(typescript@5.4.5)': + '@usecannon/cli@2.23.0(typescript@5.4.5)': dependencies: '@iarna/toml': 3.0.0 - '@usecannon/builder': 2.22.0(typescript@5.4.5) - abitype: 1.0.8(typescript@5.4.5)(zod@3.25.7) + '@usecannon/builder': 2.23.0(typescript@5.4.5) + abitype: 1.0.8(typescript@5.4.5)(zod@3.25.32) chalk: 4.1.2 commander: 12.1.0 debug: 4.4.1 @@ -1742,9 +1748,9 @@ snapshots: table: 6.9.0 tildify: 3.0.0 untildify: 4.0.0 - viem: 2.30.0(typescript@5.4.5)(zod@3.25.7) - znv: 0.4.0(zod@3.25.7) - zod: 3.25.7 + viem: 2.30.3(typescript@5.4.5)(zod@3.25.32) + znv: 0.4.0(zod@3.25.32) + zod: 3.25.32 transitivePeerDependencies: - bufferutil - encoding @@ -1763,10 +1769,10 @@ snapshots: '@usecannon/web-solc@0.5.1': {} - abitype@1.0.8(typescript@5.4.5)(zod@3.25.7): + abitype@1.0.8(typescript@5.4.5)(zod@3.25.32): optionalDependencies: typescript: 5.4.5 - zod: 3.25.7 + zod: 3.25.32 acorn@8.14.1: {} @@ -2087,7 +2093,7 @@ snapshots: glob@11.0.2: dependencies: foreground-child: 3.3.1 - jackspeak: 4.1.0 + jackspeak: 4.1.1 minimatch: 10.0.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 @@ -2237,9 +2243,9 @@ snapshots: isexe@2.0.0: {} - isows@1.0.7(ws@8.18.1): + isows@1.0.7(ws@8.18.2): dependencies: - ws: 8.18.1 + ws: 8.18.2 it-all@1.0.6: {} @@ -2251,7 +2257,7 @@ snapshots: dependencies: it-batch: 1.0.9 - jackspeak@4.1.0: + jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 @@ -2420,14 +2426,15 @@ snapshots: outdent@0.5.0: {} - ox@0.6.9(typescript@5.4.5)(zod@3.25.7): + ox@0.7.1(typescript@5.4.5)(zod@3.25.32): dependencies: '@adraffy/ens-normalize': 1.11.0 - '@noble/curves': 1.8.2 - '@noble/hashes': 1.7.2 - '@scure/bip32': 1.6.2 - '@scure/bip39': 1.5.4 - abitype: 1.0.8(typescript@5.4.5)(zod@3.25.7) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.0.8(typescript@5.4.5)(zod@3.25.32) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.4.5 @@ -2506,7 +2513,7 @@ snapshots: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/long': 4.0.2 - '@types/node': 22.15.19 + '@types/node': 22.15.23 long: 4.0.0 proxy-from-env@1.1.0: {} @@ -2738,16 +2745,16 @@ snapshots: varint@6.0.0: {} - viem@2.30.0(typescript@5.4.5)(zod@3.25.7): + viem@2.30.3(typescript@5.4.5)(zod@3.25.32): dependencies: - '@noble/curves': 1.8.2 - '@noble/hashes': 1.7.2 - '@scure/bip32': 1.6.2 - '@scure/bip39': 1.5.4 - abitype: 1.0.8(typescript@5.4.5)(zod@3.25.7) - isows: 1.0.7(ws@8.18.1) - ox: 0.6.9(typescript@5.4.5)(zod@3.25.7) - ws: 8.18.1 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.0.8(typescript@5.4.5)(zod@3.25.32) + isows: 1.0.7(ws@8.18.2) + ox: 0.7.1(typescript@5.4.5)(zod@3.25.32) + ws: 8.18.2 optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: @@ -2778,7 +2785,7 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - ws@8.18.1: {} + ws@8.18.2: {} ws@8.9.0: {} @@ -2790,9 +2797,9 @@ snapshots: yargs-parser@20.2.9: {} - znv@0.4.0(zod@3.25.7): + znv@0.4.0(zod@3.25.32): dependencies: colorette: 2.0.20 - zod: 3.25.7 + zod: 3.25.32 - zod@3.25.7: {} + zod@3.25.32: {} diff --git a/src/access/IOwnable.sol b/src/access/IOwnable.sol index 74c6553d..f827da16 100644 --- a/src/access/IOwnable.sol +++ b/src/access/IOwnable.sol @@ -5,7 +5,15 @@ pragma solidity ^0.8.8; /// @notice The interface of OpenZeppelin's `Ownable` contract. interface IOwnable { + /// @notice Get address of current owner. function owner() external view returns (address); + + /// @notice Renounce the ownership. + /// @dev Can only be called by the current owner. function renounceOwnership() external; + + /// @notice Transfer ownership to a new owner. + /// @param newOwner The address of the new owner + /// @dev Can only be called by the current owner. function transferOwnership(address newOwner) external; } diff --git a/src/dapp/Application.sol b/src/dapp/Application.sol index d034b803..45b07853 100644 --- a/src/dapp/Application.sol +++ b/src/dapp/Application.sol @@ -11,33 +11,39 @@ import {Outputs} from "../common/Outputs.sol"; import {LibAddress} from "../library/LibAddress.sol"; import {IOwnable} from "../access/IOwnable.sol"; -import {Ownable} from "@openzeppelin-contracts-5.2.0/access/Ownable.sol"; import {ERC721Holder} from "@openzeppelin-contracts-5.2.0/token/ERC721/utils/ERC721Holder.sol"; import {ERC1155Holder} from "@openzeppelin-contracts-5.2.0/token/ERC1155/utils/ERC1155Holder.sol"; -import {ReentrancyGuard} from "@openzeppelin-contracts-5.2.0/utils/ReentrancyGuard.sol"; +import {ReentrancyGuardTransient} from + "@openzeppelin-contracts-5.2.0/utils/ReentrancyGuardTransient.sol"; import {IERC721Receiver} from "@openzeppelin-contracts-5.2.0/token/ERC721/IERC721Receiver.sol"; import {BitMaps} from "@openzeppelin-contracts-5.2.0/utils/structs/BitMaps.sol"; +import {Clones} from "@openzeppelin-contracts-5.2.0/proxy/Clones.sol"; contract Application is IApplication, - Ownable, ERC721Holder, ERC1155Holder, - ReentrancyGuard + ReentrancyGuardTransient { + using Clones for address; using BitMaps for BitMaps.BitMap; using LibAddress for address; using LibOutputValidityProof for OutputValidityProof; - /// @notice Deployment block number - uint256 immutable _deploymentBlockNumber = block.number; + /// @notice Arguments embedded into the proxy contract's bytecode + struct Args { + bytes32 templateHash; + bytes dataAvailability; + } - /// @notice The initial machine state hash. - /// @dev See the `getTemplateHash` function. - bytes32 internal immutable _templateHash; + /// @notice Whether the proxy has been initialized already. + bool internal _initialized; + + /// @notice Deployment block number + uint256 internal _deploymentBlockNumber; /// @notice Keeps track of which outputs have been executed. /// @dev See the `wasOutputExecuted` function. @@ -47,24 +53,24 @@ contract Application is /// @dev See the `getOutputsMerkleRootValidator` and `migrateToOutputsMerkleRootValidator` functions. IOutputsMerkleRootValidator internal _outputsMerkleRootValidator; - /// @notice The data availability solution. - /// @dev See the `getDataAvailability` function. - bytes internal _dataAvailability; + /// @notice Application owner + /// @dev See the `owner`, `transferOwnership` and `renounceOwnership` functions. + address internal _owner; - /// @notice Creates an `Application` contract. + /// @notice Initialize an `Application` contract proxy. /// @param outputsMerkleRootValidator The initial outputs Merkle root validator contract /// @param initialOwner The initial application owner - /// @param templateHash The initial machine state hash /// @dev Reverts if the initial application owner address is zero. - constructor( + function initialize( IOutputsMerkleRootValidator outputsMerkleRootValidator, - address initialOwner, - bytes32 templateHash, - bytes memory dataAvailability - ) Ownable(initialOwner) { - _templateHash = templateHash; + address initialOwner + ) external { + assert(!_initialized); + _ensureNewOwnerIsValid(initialOwner); + _deploymentBlockNumber = block.number; _outputsMerkleRootValidator = outputsMerkleRootValidator; - _dataAvailability = dataAvailability; + _transferOwnership(initialOwner); + _initialized = true; } /// @notice Accept Ether transfers. @@ -153,7 +159,7 @@ contract Application is /// @inheritdoc IApplication function getTemplateHash() external view override returns (bytes32) { - return _templateHash; + return _args().templateHash; } /// @inheritdoc IApplication @@ -168,7 +174,7 @@ contract Application is /// @inheritdoc IApplication function getDataAvailability() external view override returns (bytes memory) { - return _dataAvailability; + return _args().dataAvailability; } /// @inheritdoc IApplication @@ -176,19 +182,26 @@ contract Application is return _deploymentBlockNumber; } - /// @inheritdoc Ownable - function owner() public view override(IOwnable, Ownable) returns (address) { - return super.owner(); + /// @inheritdoc IOwnable + function owner() external view override returns (address) { + return _owner; } - /// @inheritdoc Ownable - function renounceOwnership() public override(IOwnable, Ownable) { - super.renounceOwnership(); + /// @inheritdoc IOwnable + function renounceOwnership() external override onlyOwner { + _transferOwnership(address(0)); } - /// @inheritdoc Ownable - function transferOwnership(address newOwner) public override(IOwnable, Ownable) { - super.transferOwnership(newOwner); + /// @inheritdoc IOwnable + function transferOwnership(address newOwner) public override onlyOwner { + _ensureNewOwnerIsValid(newOwner); + _transferOwnership(newOwner); + } + + /// @notice Makes a function permissioned. + modifier onlyOwner() { + _ensureSenderIsOwner(); + _; } /// @notice Check if an outputs Merkle root is valid, @@ -233,4 +246,26 @@ contract Application is destination.safeDelegateCall(payload); } + + /// @notice Get the initialization arguments embedded into the proxy's bytecode + function _args() internal view returns (Args memory) { + return abi.decode(address(this).fetchCloneArgs(), (Args)); + } + + /// @notice Transfer ownership without checking arguments or message sender. + function _transferOwnership(address newOwner) internal { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + + /// @notice Revert if the message sender is not the owner. + function _ensureSenderIsOwner() internal view { + require(msg.sender == _owner, OwnableUnauthorizedAccount(msg.sender)); + } + + /// @notice Revert if the new owner address is the zero address. + function _ensureNewOwnerIsValid(address newOwner) internal pure { + require(newOwner != address(0), OwnableInvalidOwner(newOwner)); + } } diff --git a/src/dapp/ApplicationFactory.sol b/src/dapp/ApplicationFactory.sol index eae26f51..63401329 100644 --- a/src/dapp/ApplicationFactory.sol +++ b/src/dapp/ApplicationFactory.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.8; -import {Create2} from "@openzeppelin-contracts-5.2.0/utils/Create2.sol"; +import {Clones} from "@openzeppelin-contracts-5.2.0/proxy/Clones.sol"; import {IApplicationFactory} from "./IApplicationFactory.sol"; import {IOutputsMerkleRootValidator} from "../consensus/IOutputsMerkleRootValidator.sol"; @@ -13,15 +13,25 @@ import {IApplication} from "./IApplication.sol"; /// @title Application Factory /// @notice Allows anyone to reliably deploy a new `IApplication` contract. contract ApplicationFactory is IApplicationFactory { + using Clones for address; + + Application immutable _impl; + + constructor(Application impl) { + _impl = impl; + } + function newApplication( IOutputsMerkleRootValidator outputsMerkleRootValidator, address appOwner, bytes32 templateHash, bytes calldata dataAvailability ) external override returns (IApplication) { - IApplication appContract = new Application( - outputsMerkleRootValidator, appOwner, templateHash, dataAvailability - ); + Application.Args memory args = _buildArgs(templateHash, dataAvailability); + + address clone = address(_impl).cloneWithImmutableArgs(abi.encode(args)); + Application appContract = Application(payable(clone)); + appContract.initialize(outputsMerkleRootValidator, appOwner); emit ApplicationCreated( outputsMerkleRootValidator, @@ -41,9 +51,14 @@ contract ApplicationFactory is IApplicationFactory { bytes calldata dataAvailability, bytes32 salt ) external override returns (IApplication) { - IApplication appContract = new Application{salt: salt}( - outputsMerkleRootValidator, appOwner, templateHash, dataAvailability - ); + salt = _computeSalt(outputsMerkleRootValidator, appOwner, salt); + + Application.Args memory args = _buildArgs(templateHash, dataAvailability); + + address clone = + address(_impl).cloneDeterministicWithImmutableArgs(abi.encode(args), salt); + Application appContract = Application(payable(clone)); + appContract.initialize(outputsMerkleRootValidator, appOwner); emit ApplicationCreated( outputsMerkleRootValidator, @@ -63,19 +78,31 @@ contract ApplicationFactory is IApplicationFactory { bytes calldata dataAvailability, bytes32 salt ) external view override returns (address) { - return Create2.computeAddress( - salt, - keccak256( - abi.encodePacked( - type(Application).creationCode, - abi.encode( - outputsMerkleRootValidator, - appOwner, - templateHash, - dataAvailability - ) - ) - ) + salt = _computeSalt(outputsMerkleRootValidator, appOwner, salt); + + Application.Args memory args = _buildArgs(templateHash, dataAvailability); + + return address(_impl).predictDeterministicAddressWithImmutableArgs( + abi.encode(args), salt ); } + + function _buildArgs(bytes32 templateHash, bytes calldata dataAvailability) + internal + pure + returns (Application.Args memory) + { + return Application.Args({ + templateHash: templateHash, + dataAvailability: dataAvailability + }); + } + + function _computeSalt( + IOutputsMerkleRootValidator outputsMerkleRootValidator, + address appOwner, + bytes32 givenSalt + ) internal pure returns (bytes32) { + return keccak256(abi.encode(outputsMerkleRootValidator, appOwner, givenSalt)); + } } diff --git a/src/dapp/IApplication.sol b/src/dapp/IApplication.sol index 6b45e45c..f891e083 100644 --- a/src/dapp/IApplication.sol +++ b/src/dapp/IApplication.sol @@ -38,6 +38,10 @@ interface IApplication is IOwnable { /// @param output The output event OutputExecuted(uint64 outputIndex, bytes output); + /// @notice The owner has transferred the ownership to another address + /// or renounced the ownership (if `newOwner` is `address(0)`). + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + // Errors /// @notice Could not execute an output, because the application contract doesn't know how to. @@ -60,6 +64,12 @@ interface IApplication is IOwnable { /// @notice Raised when the computed outputs Merkle root is invalid, according to the current outputs Merkle root validator. error InvalidOutputsMerkleRoot(bytes32 outputsMerkleRoot); + /// @notice Tried to call a permissioned function (message sender must be the owner). + error OwnableUnauthorizedAccount(address account); + + /// @notice Tried to transfer the ownership to an invalid owner (e.g. `address(0)`). + error OwnableInvalidOwner(address owner); + // Permissioned functions /// @notice Migrate the application to a new outputs Merkle root validator. diff --git a/test/dapp/Application.t.sol b/test/dapp/Application.t.sol index eabbf8e7..64120282 100644 --- a/test/dapp/Application.t.sol +++ b/test/dapp/Application.t.sol @@ -4,9 +4,11 @@ pragma solidity ^0.8.22; import {Application} from "src/dapp/Application.sol"; +import {ApplicationFactory} from "src/dapp/ApplicationFactory.sol"; import {Authority} from "src/consensus/authority/Authority.sol"; import {CanonicalMachine} from "src/common/CanonicalMachine.sol"; import {IApplication} from "src/dapp/IApplication.sol"; +import {IApplicationFactory} from "src/dapp/IApplicationFactory.sol"; import {IOutputsMerkleRootValidator} from "src/consensus/IOutputsMerkleRootValidator.sol"; import {OutputValidityProof} from "src/common/OutputValidityProof.sol"; import {Outputs} from "src/common/Outputs.sol"; @@ -45,6 +47,7 @@ contract ApplicationTest is Test, OwnableTest { using ExternalLibMerkle32 for bytes32[]; using LibAddressArray for Vm; + IApplicationFactory _appFactory; IApplication _appContract; EtherReceiver _etherReceiver; Authority _authority; @@ -96,7 +99,7 @@ contract ApplicationTest is Test, OwnableTest { vm.expectRevert( abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0)) ); - new Application(_authority, address(0), _templateHash, new bytes(0)); + _appFactory.newApplication(_authority, address(0), _templateHash, new bytes(0)); } function testConstructor( @@ -113,7 +116,7 @@ contract ApplicationTest is Test, OwnableTest { vm.expectEmit(true, true, false, false); emit Ownable.OwnershipTransferred(address(0), owner); - IApplication appContract = new Application( + IApplication appContract = _appFactory.newApplication( outputsMerkleRootValidator, owner, templateHash, dataAvailability ); @@ -345,8 +348,10 @@ contract ApplicationTest is Test, OwnableTest { _inputBox = new InputBox(); _authority = new Authority(_authorityOwner, _epochLength); _dataAvailability = abi.encodeCall(DataAvailability.InputBox, (_inputBox)); - _appContract = - new Application(_authority, _appOwner, _templateHash, _dataAvailability); + _appFactory = new ApplicationFactory(new Application()); + _appContract = _appFactory.newApplication( + _authority, _appOwner, _templateHash, _dataAvailability + ); _safeERC20Transfer = new SafeERC20Transfer(); } @@ -539,7 +544,9 @@ contract ApplicationTest is Test, OwnableTest { vm.expectRevert( abi.encodeWithSelector( - IApplication.InsufficientFunds.selector, _transferAmount, 0 + IApplication.InsufficientFunds.selector, + _transferAmount, + address(_appContract).balance ) ); _appContract.executeOutput(output, proof); @@ -580,7 +587,13 @@ contract ApplicationTest is Test, OwnableTest { "Application contract does not have enough Ether" ); - vm.expectRevert(); + vm.expectRevert( + abi.encodeWithSelector( + IApplication.InsufficientFunds.selector, + _transferAmount, + address(_appContract).balance + ) + ); _appContract.executeOutput(output, proof); vm.deal(address(_appContract), _transferAmount); diff --git a/test/dapp/ApplicationFactory.t.sol b/test/dapp/ApplicationFactory.t.sol index 4dc28b0a..de274a04 100644 --- a/test/dapp/ApplicationFactory.t.sol +++ b/test/dapp/ApplicationFactory.t.sol @@ -7,16 +7,19 @@ pragma solidity ^0.8.22; import {ApplicationFactory} from "src/dapp/ApplicationFactory.sol"; import {IApplicationFactory} from "src/dapp/IApplicationFactory.sol"; import {IApplication} from "src/dapp/IApplication.sol"; +import {Application} from "src/dapp/Application.sol"; import {IOutputsMerkleRootValidator} from "src/consensus/IOutputsMerkleRootValidator.sol"; import {Test} from "forge-std-1.9.6/src/Test.sol"; import {Vm} from "forge-std-1.9.6/src/Vm.sol"; +import {Errors} from "@openzeppelin-contracts-5.2.0/utils/Errors.sol"; + contract ApplicationFactoryTest is Test { ApplicationFactory _factory; function setUp() public { - _factory = new ApplicationFactory(); + _factory = new ApplicationFactory(new Application()); } function testNewApplication( @@ -84,7 +87,7 @@ contract ApplicationFactoryTest is Test { assertEq(precalculatedAddress, address(appContract)); // Cannot deploy an application with the same salt twice - vm.expectRevert(bytes("")); + vm.expectRevert(Errors.FailedDeployment.selector); _factory.newApplication( outputsMerkleRootValidator, appOwner, templateHash, dataAvailability, salt ); diff --git a/test/dapp/SelfHostedApplicationFactory.t.sol b/test/dapp/SelfHostedApplicationFactory.t.sol index 2f7a0257..87184964 100644 --- a/test/dapp/SelfHostedApplicationFactory.t.sol +++ b/test/dapp/SelfHostedApplicationFactory.t.sol @@ -12,6 +12,7 @@ import {IAuthority} from "src/consensus/authority/IAuthority.sol"; import {IApplicationFactory} from "src/dapp/IApplicationFactory.sol"; import {ApplicationFactory} from "src/dapp/ApplicationFactory.sol"; import {IApplication} from "src/dapp/IApplication.sol"; +import {Application} from "src/dapp/Application.sol"; import {ISelfHostedApplicationFactory} from "src/dapp/ISelfHostedApplicationFactory.sol"; import {SelfHostedApplicationFactory} from "src/dapp/SelfHostedApplicationFactory.sol"; @@ -24,7 +25,7 @@ contract SelfHostedApplicationFactoryTest is Test { function setUp() external { authorityFactory = new AuthorityFactory(); - applicationFactory = new ApplicationFactory(); + applicationFactory = new ApplicationFactory(new Application()); factory = new SelfHostedApplicationFactory(authorityFactory, applicationFactory); }