From e26b5272a0ba11cfeb08c0a4c32fc9164c827704 Mon Sep 17 00:00:00 2001 From: Guilherme Dantas Date: Mon, 22 Jan 2024 18:29:20 -0300 Subject: [PATCH] feat!: unify trees in `OutputValidityProof` --- onchain/rollups/.changeset/sharp-yaks-sing.md | 5 +++ .../rollups/.changeset/thin-wolves-give.md | 9 +++++ .../contracts/common/OutputEncoding.sol | 29 -------------- .../contracts/common/OutputValidityProof.sol | 6 +-- onchain/rollups/contracts/common/Outputs.sol | 20 ++++++++++ .../contracts/library/LibOutputValidation.sol | 32 +++++---------- .../test/foundry/dapp/Application.t.sol | 39 ++++++++++++------- 7 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 onchain/rollups/.changeset/sharp-yaks-sing.md create mode 100644 onchain/rollups/.changeset/thin-wolves-give.md delete mode 100644 onchain/rollups/contracts/common/OutputEncoding.sol create mode 100644 onchain/rollups/contracts/common/Outputs.sol diff --git a/onchain/rollups/.changeset/sharp-yaks-sing.md b/onchain/rollups/.changeset/sharp-yaks-sing.md new file mode 100644 index 00000000..a7bffc64 --- /dev/null +++ b/onchain/rollups/.changeset/sharp-yaks-sing.md @@ -0,0 +1,5 @@ +--- +"@cartesi/rollups": minor +--- + +Added `Outputs` interface. diff --git a/onchain/rollups/.changeset/thin-wolves-give.md b/onchain/rollups/.changeset/thin-wolves-give.md new file mode 100644 index 00000000..40309bb0 --- /dev/null +++ b/onchain/rollups/.changeset/thin-wolves-give.md @@ -0,0 +1,9 @@ +--- +"@cartesi/rollups": major +--- + +Modified the `OutputValidityProof` struct: + +- Removed the `vouchersEpochRootHash` field +- Removed the `noticesEpochRootHash` field +- Added an `outputsEpochRootHash` field diff --git a/onchain/rollups/contracts/common/OutputEncoding.sol b/onchain/rollups/contracts/common/OutputEncoding.sol deleted file mode 100644 index a009ee0e..00000000 --- a/onchain/rollups/contracts/common/OutputEncoding.sol +++ /dev/null @@ -1,29 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -pragma solidity ^0.8.8; - -/// @title Output Encoding Library -/// -/// @notice Defines the encoding of outputs generated by the off-chain machine. -library OutputEncoding { - /// @notice Encode a notice. - /// @param notice The notice - /// @return The encoded output - function encodeNotice( - bytes calldata notice - ) internal pure returns (bytes memory) { - return abi.encode(notice); - } - - /// @notice Encode a voucher. - /// @param destination The address that will receive the payload through a message call - /// @param payload The payload, which—in the case of Solidity contracts—encodes a function call - /// @return The encoded output - function encodeVoucher( - address destination, - bytes calldata payload - ) internal pure returns (bytes memory) { - return abi.encode(destination, payload); - } -} diff --git a/onchain/rollups/contracts/common/OutputValidityProof.sol b/onchain/rollups/contracts/common/OutputValidityProof.sol index 92878f6f..8ad68a9b 100644 --- a/onchain/rollups/contracts/common/OutputValidityProof.sol +++ b/onchain/rollups/contracts/common/OutputValidityProof.sol @@ -6,8 +6,7 @@ pragma solidity ^0.8.8; /// @param inputIndexWithinEpoch Which input, inside the epoch, the output belongs to /// @param outputIndexWithinInput Index of output emitted by the input /// @param outputHashesRootHash Merkle root of hashes of outputs emitted by the input -/// @param vouchersEpochRootHash Merkle root of all epoch's voucher metadata hashes -/// @param noticesEpochRootHash Merkle root of all epoch's notice metadata hashes +/// @param outputsEpochRootHash Merkle root of all epoch's outputs metadata hashes /// @param machineStateHash Hash of the machine state claimed this epoch /// @param outputHashInOutputHashesSiblings Proof that this output metadata is in metadata memory range /// @param outputHashesInEpochSiblings Proof that this output metadata is in epoch's output memory range @@ -15,8 +14,7 @@ struct OutputValidityProof { uint64 inputIndexWithinEpoch; uint64 outputIndexWithinInput; bytes32 outputHashesRootHash; - bytes32 vouchersEpochRootHash; - bytes32 noticesEpochRootHash; + bytes32 outputsEpochRootHash; bytes32 machineStateHash; bytes32[] outputHashInOutputHashesSiblings; bytes32[] outputHashesInEpochSiblings; diff --git a/onchain/rollups/contracts/common/Outputs.sol b/onchain/rollups/contracts/common/Outputs.sol new file mode 100644 index 00000000..cc965ecb --- /dev/null +++ b/onchain/rollups/contracts/common/Outputs.sol @@ -0,0 +1,20 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +pragma solidity ^0.8.8; + +/// @title Outputs +/// @notice Defines the signatures of outputs that can be generated by the +/// off-chain machine and verified by the on-chain contracts. +interface Outputs { + /// @notice A piece of verifiable information. + /// @param notice An arbitrary blob. + function Notice(bytes calldata notice) external; + + /// @notice A single-use permission to execute a specific message call + /// from the context of the application contract. + /// @param destination The address that will be called + /// @param payload The payload, which—in the case of Solidity + /// contracts—encodes a function call + function Voucher(address destination, bytes calldata payload) external; +} diff --git a/onchain/rollups/contracts/library/LibOutputValidation.sol b/onchain/rollups/contracts/library/LibOutputValidation.sol index 3049a8e9..705b09fe 100644 --- a/onchain/rollups/contracts/library/LibOutputValidation.sol +++ b/onchain/rollups/contracts/library/LibOutputValidation.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.8; import {CanonicalMachine} from "../common/CanonicalMachine.sol"; import {IApplication} from "../dapp/IApplication.sol"; import {MerkleV2} from "@cartesi/util/contracts/MerkleV2.sol"; -import {OutputEncoding} from "../common/OutputEncoding.sol"; +import {Outputs} from "../common/Outputs.sol"; import {OutputValidityProof} from "../common/OutputValidityProof.sol"; /// @title Output Validation Library @@ -17,22 +17,15 @@ library LibOutputValidation { /// @param v The output validity proof /// @param encodedOutput The encoded output /// @param epochHash The hash of the epoch in which the output was generated - /// @param outputsEpochRootHash Either `v.vouchersEpochRootHash` (for vouchers) - /// or `v.noticesEpochRootHash` (for notices) function validateEncodedOutput( OutputValidityProof calldata v, bytes memory encodedOutput, - bytes32 epochHash, - bytes32 outputsEpochRootHash + bytes32 epochHash ) internal pure { // prove that outputs hash is represented in a finalized epoch if ( keccak256( - abi.encodePacked( - v.vouchersEpochRootHash, - v.noticesEpochRootHash, - v.machineStateHash - ) + abi.encodePacked(v.outputsEpochRootHash, v.machineStateHash) ) != epochHash ) { revert IApplication.IncorrectEpochHash(); @@ -49,7 +42,7 @@ library LibOutputValidation { CanonicalMachine.EPOCH_OUTPUT_LOG2_SIZE.uint64OfSize(), v.outputHashesRootHash, v.outputHashesInEpochSiblings - ) != outputsEpochRootHash + ) != v.outputsEpochRootHash ) { revert IApplication.IncorrectOutputsEpochRootHash(); } @@ -72,7 +65,7 @@ library LibOutputValidation { // is contained in it. We can't simply use hashOfOutput because the // log2size of the leaf is three (8 bytes) not five (32 bytes) bytes32 merkleRootOfHashOfOutput = MerkleV2.getMerkleRootFromBytes( - abi.encodePacked(keccak256(encodedOutput)), + abi.encodePacked(keccak256(abi.encode(encodedOutput))), CanonicalMachine.KECCAK_LOG2_SIZE.uint64OfSize() ); @@ -105,15 +98,10 @@ library LibOutputValidation { bytes calldata payload, bytes32 epochHash ) internal pure { - bytes memory encodedVoucher = OutputEncoding.encodeVoucher( - destination, - payload - ); validateEncodedOutput( v, - encodedVoucher, - epochHash, - v.vouchersEpochRootHash + abi.encodeCall(Outputs.Voucher, (destination, payload)), + epochHash ); } @@ -126,12 +114,10 @@ library LibOutputValidation { bytes calldata notice, bytes32 epochHash ) internal pure { - bytes memory encodedNotice = OutputEncoding.encodeNotice(notice); validateEncodedOutput( v, - encodedNotice, - epochHash, - v.noticesEpochRootHash + abi.encodeCall(Outputs.Notice, (notice)), + epochHash ); } } diff --git a/onchain/rollups/test/foundry/dapp/Application.t.sol b/onchain/rollups/test/foundry/dapp/Application.t.sol index d532bd74..e8c4c31a 100644 --- a/onchain/rollups/test/foundry/dapp/Application.t.sol +++ b/onchain/rollups/test/foundry/dapp/Application.t.sol @@ -15,7 +15,7 @@ import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; import {LibOutputValidation} from "contracts/library/LibOutputValidation.sol"; import {LibProof} from "contracts/library/LibProof.sol"; import {OutputValidityProof} from "contracts/common/OutputValidityProof.sol"; -import {OutputEncoding} from "contracts/common/OutputEncoding.sol"; +import {Outputs} from "contracts/common/Outputs.sol"; import {InputRange} from "contracts/common/InputRange.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; @@ -69,7 +69,7 @@ contract ApplicationTest is TestBase { bytes _encodedFinishEpochResponse; IInputBox immutable _inputBox; address immutable _appOwner; - address immutable _noticeSender; + address immutable _inputSender; address immutable _recipient; address immutable _tokenOwner; bytes32 immutable _salt; @@ -98,7 +98,7 @@ contract ApplicationTest is TestBase { _appOwner = LibBytes.hashToAddress("appOwner"); _initialSupply = LibBytes.hashToUint256("initialSupply"); _inputBox = IInputBox(LibBytes.hashToAddress("inputBox")); - _noticeSender = LibBytes.hashToAddress("noticeSender"); + _inputSender = LibBytes.hashToAddress("inputSender"); _recipient = LibBytes.hashToAddress("recipient"); _salt = keccak256("salt"); _templateHash = keccak256("templateHash"); @@ -330,7 +330,7 @@ contract ApplicationTest is TestBase { OutputName.ERC20TransferVoucher ); - proof.validity.vouchersEpochRootHash = bytes32(uint256(0xdeadbeef)); + proof.validity.outputsEpochRootHash = bytes32(uint256(0xdeadbeef)); vm.expectRevert(IApplication.IncorrectEpochHash.selector); _executeVoucher(voucher, proof); @@ -686,22 +686,37 @@ contract ApplicationTest is TestBase { ); } + function _encodeVoucher( + Voucher memory voucher + ) internal pure returns (bytes memory) { + return + abi.encodeCall( + Outputs.Voucher, + (voucher.destination, voucher.payload) + ); + } + + function _encodeNotice( + bytes memory notice + ) internal pure returns (bytes memory) { + return abi.encodeCall(Outputs.Notice, (notice)); + } + function _writeInputs() internal { for (uint256 i; i < _outputEnums.length; ++i) { LibServerManager.OutputEnum outputEnum = _outputEnums[i]; if (outputEnum == LibServerManager.OutputEnum.VOUCHER) { Voucher memory voucher = _getVoucher(i); - _writeInput(i, voucher.destination, voucher.payload); + _writeInput(i, _encodeVoucher(voucher)); } else { bytes memory notice = _getNotice(i); - _writeInput(i, _noticeSender, notice); + _writeInput(i, _encodeNotice(notice)); } } } function _writeInput( uint256 inputIndexWithinEpoch, - address sender, bytes memory payload ) internal { string memory inputIndexWithinEpochStr = vm.toString( @@ -711,7 +726,7 @@ contract ApplicationTest is TestBase { "input", inputIndexWithinEpochStr ); - vm.serializeAddress(objectKey, "sender", sender); + vm.serializeAddress(objectKey, "sender", _inputSender); string memory json = vm.serializeBytes(objectKey, "payload", payload); string memory path = _getInputPath(inputIndexWithinEpochStr); vm.writeJson(json, path); @@ -762,7 +777,7 @@ contract ApplicationTest is TestBase { OutputName outputName ) internal returns (Proof memory) { uint256 inputIndexWithinEpoch = uint256(outputName); - Proof memory proof = _getVoucherProof(inputIndexWithinEpoch); + Proof memory proof = _getNoticeProof(inputIndexWithinEpoch); _mockConsensus(proof); return proof; } @@ -963,8 +978,7 @@ contract ApplicationTest is TestBase { return keccak256( abi.encodePacked( - validity.vouchersEpochRootHash, - validity.noticesEpochRootHash, + validity.outputsEpochRootHash, validity.machineStateHash ) ); @@ -978,8 +992,7 @@ contract ApplicationTest is TestBase { inputIndexWithinEpoch: v.inputIndexWithinEpoch.toUint64(), outputIndexWithinInput: v.outputIndexWithinInput.toUint64(), outputHashesRootHash: v.outputHashesRootHash, - vouchersEpochRootHash: v.vouchersEpochRootHash, - noticesEpochRootHash: v.noticesEpochRootHash, + outputsEpochRootHash: v.noticesEpochRootHash, machineStateHash: v.machineStateHash, outputHashInOutputHashesSiblings: v .outputHashInOutputHashesSiblings,