From 846a99b09c04d1aba60886126aa02a3bc353087d Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Fri, 13 Dec 2024 20:37:54 -0300 Subject: [PATCH 1/2] feat: add `getDataAvailability` function to `IApplication` --- .changeset/early-boats-remember.md | 5 +++ contracts/common/DataAvailability.sol | 28 ++++++++++++++ contracts/dapp/Application.sol | 17 ++++++++- contracts/dapp/ApplicationFactory.sol | 34 ++++++++++++++--- contracts/dapp/IApplication.sol | 5 +++ contracts/dapp/IApplicationFactory.sol | 6 ++- .../dapp/ISelfHostedApplicationFactory.sol | 2 + .../dapp/SelfHostedApplicationFactory.sol | 4 ++ test/dapp/Application.t.sol | 26 +++++++++++-- test/dapp/ApplicationFactory.t.sol | 38 ++++++++++++++++--- test/dapp/SelfHostedApplicationFactory.t.sol | 10 +++++ 11 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 .changeset/early-boats-remember.md create mode 100644 contracts/common/DataAvailability.sol diff --git a/.changeset/early-boats-remember.md b/.changeset/early-boats-remember.md new file mode 100644 index 00000000..d74b84e1 --- /dev/null +++ b/.changeset/early-boats-remember.md @@ -0,0 +1,5 @@ +--- +"@cartesi/rollups": minor +--- + +Add data availability configuration to application contract diff --git a/contracts/common/DataAvailability.sol b/contracts/common/DataAvailability.sol new file mode 100644 index 00000000..a9b0a847 --- /dev/null +++ b/contracts/common/DataAvailability.sol @@ -0,0 +1,28 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +pragma solidity ^0.8.8; + +import {IInputBox} from "../inputs/IInputBox.sol"; + +/// @title Data Availability +/// @notice Defines the signatures of data availability solutions. +interface DataAvailability { + /// @notice The application receives inputs only from + /// a contract that implements the `IInputBox` interface. + /// @param inputBox The input box contract address + function InputBox(IInputBox inputBox) external; + + /// @notice The application receives inputs from + /// a contract that implements the `IInputBox` interface, + /// and from Espresso, starting from a given block height, + /// and for a given namespace ID. + /// @param inputBox The input box contract address + /// @param fromBlock Height of first Espresso block to consider + /// @param namespaceId The Espresso namespace ID + function InputBoxAndEspresso( + IInputBox inputBox, + uint256 fromBlock, + uint32 namespaceId + ) external; +} diff --git a/contracts/dapp/Application.sol b/contracts/dapp/Application.sol index 41cc1a6e..04fb4fb1 100644 --- a/contracts/dapp/Application.sol +++ b/contracts/dapp/Application.sol @@ -41,6 +41,10 @@ contract Application is /// @dev See the `getConsensus` and `migrateToConsensus` functions. IConsensus internal _consensus; + /// @notice The data availability solution. + /// @dev See the `getDataAvailability` function. + bytes internal _dataAvailability; + /// @notice Creates an `Application` contract. /// @param consensus The initial consensus contract /// @param initialOwner The initial application owner @@ -49,10 +53,12 @@ contract Application is constructor( IConsensus consensus, address initialOwner, - bytes32 templateHash + bytes32 templateHash, + bytes memory dataAvailability ) Ownable(initialOwner) { _templateHash = templateHash; _consensus = consensus; + _dataAvailability = dataAvailability; } /// @notice Accept Ether transfers. @@ -136,6 +142,15 @@ contract Application is return _consensus; } + function getDataAvailability() + external + view + override + returns (bytes memory) + { + return _dataAvailability; + } + function owner() public view override(IOwnable, Ownable) returns (address) { return super.owner(); } diff --git a/contracts/dapp/ApplicationFactory.sol b/contracts/dapp/ApplicationFactory.sol index 22bb7b7c..a4740fb9 100644 --- a/contracts/dapp/ApplicationFactory.sol +++ b/contracts/dapp/ApplicationFactory.sol @@ -16,15 +16,23 @@ contract ApplicationFactory is IApplicationFactory { function newApplication( IConsensus consensus, address appOwner, - bytes32 templateHash + bytes32 templateHash, + bytes calldata dataAvailability ) external override returns (IApplication) { IApplication appContract = new Application( consensus, appOwner, - templateHash + templateHash, + dataAvailability ); - emit ApplicationCreated(consensus, appOwner, templateHash, appContract); + emit ApplicationCreated( + consensus, + appOwner, + templateHash, + dataAvailability, + appContract + ); return appContract; } @@ -33,15 +41,23 @@ contract ApplicationFactory is IApplicationFactory { IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external override returns (IApplication) { IApplication appContract = new Application{salt: salt}( consensus, appOwner, - templateHash + templateHash, + dataAvailability ); - emit ApplicationCreated(consensus, appOwner, templateHash, appContract); + emit ApplicationCreated( + consensus, + appOwner, + templateHash, + dataAvailability, + appContract + ); return appContract; } @@ -50,6 +66,7 @@ contract ApplicationFactory is IApplicationFactory { IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external view override returns (address) { return @@ -58,7 +75,12 @@ contract ApplicationFactory is IApplicationFactory { keccak256( abi.encodePacked( type(Application).creationCode, - abi.encode(consensus, appOwner, templateHash) + abi.encode( + consensus, + appOwner, + templateHash, + dataAvailability + ) ) ) ); diff --git a/contracts/dapp/IApplication.sol b/contracts/dapp/IApplication.sol index fa95aba1..b1a2ea7c 100644 --- a/contracts/dapp/IApplication.sol +++ b/contracts/dapp/IApplication.sol @@ -115,4 +115,9 @@ interface IApplication is IOwnable { /// @notice Get the current consensus. /// @return The current consensus function getConsensus() external view returns (IConsensus); + + /// @notice Get the data availability solution used by application. + /// @return Solidity ABI-encoded function call that describes + /// the source of inputs that should be fed to the application. + function getDataAvailability() external view returns (bytes memory); } diff --git a/contracts/dapp/IApplicationFactory.sol b/contracts/dapp/IApplicationFactory.sol index d55e4c6b..5dff1520 100644 --- a/contracts/dapp/IApplicationFactory.sol +++ b/contracts/dapp/IApplicationFactory.sol @@ -20,6 +20,7 @@ interface IApplicationFactory { IConsensus indexed consensus, address appOwner, bytes32 templateHash, + bytes dataAvailability, IApplication appContract ); @@ -35,7 +36,8 @@ interface IApplicationFactory { function newApplication( IConsensus consensus, address appOwner, - bytes32 templateHash + bytes32 templateHash, + bytes calldata dataAvailability ) external returns (IApplication); /// @notice Deploy a new application deterministically. @@ -50,6 +52,7 @@ interface IApplicationFactory { IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external returns (IApplication); @@ -65,6 +68,7 @@ interface IApplicationFactory { IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external view returns (address); } diff --git a/contracts/dapp/ISelfHostedApplicationFactory.sol b/contracts/dapp/ISelfHostedApplicationFactory.sol index d417b285..c1503c1b 100644 --- a/contracts/dapp/ISelfHostedApplicationFactory.sol +++ b/contracts/dapp/ISelfHostedApplicationFactory.sol @@ -37,6 +37,7 @@ interface ISelfHostedApplicationFactory { uint256 epochLength, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external returns (IApplication, IAuthority); @@ -54,6 +55,7 @@ interface ISelfHostedApplicationFactory { uint256 epochLength, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external view returns (address, address); } diff --git a/contracts/dapp/SelfHostedApplicationFactory.sol b/contracts/dapp/SelfHostedApplicationFactory.sol index e083f3b7..c8fb1e5f 100644 --- a/contracts/dapp/SelfHostedApplicationFactory.sol +++ b/contracts/dapp/SelfHostedApplicationFactory.sol @@ -50,6 +50,7 @@ contract SelfHostedApplicationFactory is ISelfHostedApplicationFactory { uint256 epochLength, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external returns (IApplication application, IAuthority authority) { authority = _authorityFactory.newAuthority( @@ -62,6 +63,7 @@ contract SelfHostedApplicationFactory is ISelfHostedApplicationFactory { authority, appOwner, templateHash, + dataAvailability, salt ); } @@ -71,6 +73,7 @@ contract SelfHostedApplicationFactory is ISelfHostedApplicationFactory { uint256 epochLength, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external view returns (address application, address authority) { authority = _authorityFactory.calculateAuthorityAddress( @@ -83,6 +86,7 @@ contract SelfHostedApplicationFactory is ISelfHostedApplicationFactory { IConsensus(authority), appOwner, templateHash, + dataAvailability, salt ); } diff --git a/test/dapp/Application.t.sol b/test/dapp/Application.t.sol index cf961ef3..e2a5497a 100644 --- a/test/dapp/Application.t.sol +++ b/test/dapp/Application.t.sol @@ -14,6 +14,9 @@ import {Outputs} from "contracts/common/Outputs.sol"; import {SafeERC20Transfer} from "contracts/delegatecall/SafeERC20Transfer.sol"; import {IOwnable} from "contracts/access/IOwnable.sol"; import {LibAddress} from "contracts/library/LibAddress.sol"; +import {InputBox} from "contracts/inputs/InputBox.sol"; +import {IInputBox} from "contracts/inputs/IInputBox.sol"; +import {DataAvailability} from "contracts/common/DataAvailability.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; @@ -45,12 +48,14 @@ contract ApplicationTest is TestBase, OwnableTest { IERC1155 _erc1155SingleToken; IERC1155 _erc1155BatchToken; SafeERC20Transfer _safeERC20Transfer; + IInputBox _inputBox; LibEmulator.State _emulator; address _appOwner; address _authorityOwner; address _recipient; address _tokenOwner; + bytes _dataAvailability; string[] _outputNames; bytes4[] _interfaceIds; uint256[] _tokenIds; @@ -90,13 +95,14 @@ contract ApplicationTest is TestBase, OwnableTest { address(0) ) ); - new Application(_consensus, address(0), _templateHash); + new Application(_consensus, address(0), _templateHash, new bytes(0)); } function testConstructor( IConsensus consensus, address owner, - bytes32 templateHash + bytes32 templateHash, + bytes calldata dataAvailability ) external { vm.assume(owner != address(0)); @@ -106,12 +112,14 @@ contract ApplicationTest is TestBase, OwnableTest { IApplication appContract = new Application( consensus, owner, - templateHash + templateHash, + dataAvailability ); assertEq(address(appContract.getConsensus()), address(consensus)); assertEq(appContract.owner(), owner); assertEq(appContract.getTemplateHash(), templateHash); + assertEq(appContract.getDataAvailability(), dataAvailability); } // ------------------- @@ -338,8 +346,18 @@ contract ApplicationTest is TestBase, OwnableTest { _tokenIds, _initialSupplies ); + _inputBox = new InputBox(); _consensus = new Authority(_authorityOwner, _epochLength); - _appContract = new Application(_consensus, _appOwner, _templateHash); + _dataAvailability = abi.encodeCall( + DataAvailability.InputBox, + (_inputBox) + ); + _appContract = new Application( + _consensus, + _appOwner, + _templateHash, + _dataAvailability + ); _safeERC20Transfer = new SafeERC20Transfer(); } diff --git a/test/dapp/ApplicationFactory.t.sol b/test/dapp/ApplicationFactory.t.sol index 0f762b53..d8d30d23 100644 --- a/test/dapp/ApplicationFactory.t.sol +++ b/test/dapp/ApplicationFactory.t.sol @@ -20,25 +20,29 @@ contract ApplicationFactoryTest is TestBase { function testNewApplication( IConsensus consensus, address appOwner, - bytes32 templateHash + bytes32 templateHash, + bytes calldata dataAvailability ) public { vm.assume(appOwner != address(0)); IApplication appContract = _factory.newApplication( consensus, appOwner, - templateHash + templateHash, + dataAvailability ); assertEq(address(appContract.getConsensus()), address(consensus)); assertEq(appContract.owner(), appOwner); assertEq(appContract.getTemplateHash(), templateHash); + assertEq(appContract.getDataAvailability(), dataAvailability); } function testNewApplicationDeterministic( IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) public { vm.assume(appOwner != address(0)); @@ -47,6 +51,7 @@ contract ApplicationFactoryTest is TestBase { consensus, appOwner, templateHash, + dataAvailability, salt ); @@ -54,6 +59,7 @@ contract ApplicationFactoryTest is TestBase { consensus, appOwner, templateHash, + dataAvailability, salt ); @@ -63,11 +69,13 @@ contract ApplicationFactoryTest is TestBase { assertEq(address(appContract.getConsensus()), address(consensus)); assertEq(appContract.owner(), appOwner); assertEq(appContract.getTemplateHash(), templateHash); + assertEq(appContract.getDataAvailability(), dataAvailability); precalculatedAddress = _factory.calculateApplicationAddress( consensus, appOwner, templateHash, + dataAvailability, salt ); @@ -76,13 +84,20 @@ contract ApplicationFactoryTest is TestBase { // Cannot deploy an application with the same salt twice vm.expectRevert(bytes("")); - _factory.newApplication(consensus, appOwner, templateHash, salt); + _factory.newApplication( + consensus, + appOwner, + templateHash, + dataAvailability, + salt + ); } function testApplicationCreatedEvent( IConsensus consensus, address appOwner, - bytes32 templateHash + bytes32 templateHash, + bytes calldata dataAvailability ) public { vm.assume(appOwner != address(0)); @@ -91,13 +106,15 @@ contract ApplicationFactoryTest is TestBase { IApplication appContract = _factory.newApplication( consensus, appOwner, - templateHash + templateHash, + dataAvailability ); _testApplicationCreatedEventAux( consensus, appOwner, templateHash, + dataAvailability, appContract ); } @@ -106,6 +123,7 @@ contract ApplicationFactoryTest is TestBase { IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) public { vm.assume(appOwner != address(0)); @@ -116,6 +134,7 @@ contract ApplicationFactoryTest is TestBase { consensus, appOwner, templateHash, + dataAvailability, salt ); @@ -123,6 +142,7 @@ contract ApplicationFactoryTest is TestBase { consensus, appOwner, templateHash, + dataAvailability, appContract ); } @@ -131,6 +151,7 @@ contract ApplicationFactoryTest is TestBase { IConsensus consensus, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, IApplication appContract ) internal { Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -155,11 +176,16 @@ contract ApplicationFactoryTest is TestBase { ( address appOwner_, bytes32 templateHash_, + bytes memory dataAvailability_, IApplication app_ - ) = abi.decode(entry.data, (address, bytes32, IApplication)); + ) = abi.decode( + entry.data, + (address, bytes32, bytes, IApplication) + ); assertEq(appOwner, appOwner_); assertEq(templateHash, templateHash_); + assertEq(dataAvailability, dataAvailability_); assertEq(address(appContract), address(app_)); } } diff --git a/test/dapp/SelfHostedApplicationFactory.t.sol b/test/dapp/SelfHostedApplicationFactory.t.sol index aabb281c..d8938c92 100644 --- a/test/dapp/SelfHostedApplicationFactory.t.sol +++ b/test/dapp/SelfHostedApplicationFactory.t.sol @@ -49,6 +49,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { uint256 epochLength, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external { vm.assume(appOwner != address(0)); @@ -65,6 +66,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { epochLength, appOwner, templateHash, + dataAvailability, salt ); } @@ -73,6 +75,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { address authorityOwner, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external { vm.assume(appOwner != address(0)); @@ -84,6 +87,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { 0, appOwner, templateHash, + dataAvailability, salt ); } @@ -92,6 +96,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { address authorityOwner, uint256 epochLength, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external { vm.assume(authorityOwner != address(0)); @@ -108,6 +113,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { epochLength, address(0), templateHash, + dataAvailability, salt ); } @@ -117,6 +123,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { uint256 epochLength, address appOwner, bytes32 templateHash, + bytes calldata dataAvailability, bytes32 salt ) external { vm.assume(appOwner != address(0)); @@ -131,6 +138,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { epochLength, appOwner, templateHash, + dataAvailability, salt ); @@ -142,6 +150,7 @@ contract SelfHostedApplicationFactoryTest is TestBase { epochLength, appOwner, templateHash, + dataAvailability, salt ); @@ -154,5 +163,6 @@ contract SelfHostedApplicationFactoryTest is TestBase { assertEq(address(application.getConsensus()), authorityAddr); assertEq(application.owner(), appOwner); assertEq(application.getTemplateHash(), templateHash); + assertEq(application.getDataAvailability(), dataAvailability); } } From 7cb08d8c75fdc8f516b7c1f57f09ccec9145f042 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Fri, 13 Dec 2024 20:47:24 -0300 Subject: [PATCH 2/2] feat: add `getDeploymentBlockNumber` function to `IInputBox` --- contracts/inputs/IInputBox.sol | 3 +++ contracts/inputs/InputBox.sol | 17 +++++++++++++++++ test/inputs/InputBox.t.sol | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/contracts/inputs/IInputBox.sol b/contracts/inputs/IInputBox.sol index 245e7c4c..396c9962 100644 --- a/contracts/inputs/IInputBox.sol +++ b/contracts/inputs/IInputBox.sol @@ -53,4 +53,7 @@ interface IInputBox { address appContract, uint256 index ) external view returns (bytes32); + + /// @notice Get number of block in which contract was deployed + function getDeploymentBlockNumber() external view returns (uint256); } diff --git a/contracts/inputs/InputBox.sol b/contracts/inputs/InputBox.sol index a3fe3cc1..e170c4c0 100644 --- a/contracts/inputs/InputBox.sol +++ b/contracts/inputs/InputBox.sol @@ -8,9 +8,16 @@ import {CanonicalMachine} from "../common/CanonicalMachine.sol"; import {Inputs} from "../common/Inputs.sol"; contract InputBox is IInputBox { + /// @notice Deployment block number + uint256 immutable _deploymentBlockNumber; + /// @notice Mapping of application contract addresses to arrays of input hashes. mapping(address => bytes32[]) private _inputBoxes; + constructor() { + _deploymentBlockNumber = block.number; + } + /// @inheritdoc IInputBox function addInput( address appContract, @@ -65,4 +72,14 @@ contract InputBox is IInputBox { ) external view override returns (bytes32) { return _inputBoxes[appContract][index]; } + + /// @inheritdoc IInputBox + function getDeploymentBlockNumber() + external + view + override + returns (uint256) + { + return _deploymentBlockNumber; + } } diff --git a/test/inputs/InputBox.t.sol b/test/inputs/InputBox.t.sol index 4430bca8..4c56ffeb 100644 --- a/test/inputs/InputBox.t.sol +++ b/test/inputs/InputBox.t.sol @@ -18,6 +18,12 @@ contract InputBoxTest is Test { _inputBox = new InputBox(); } + function testDeploymentBlockNumber(uint256 blockNumber) public { + vm.roll(blockNumber); + _inputBox = new InputBox(); + assertEq(_inputBox.getDeploymentBlockNumber(), blockNumber); + } + function testNoInputs(address appContract) public view { assertEq(_inputBox.getNumberOfInputs(appContract), 0); }