Skip to content

Commit

Permalink
feat: blockchain agnostic inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
ZzzzHui committed Nov 13, 2023
1 parent 949e219 commit 8e4c677
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 77 deletions.
7 changes: 7 additions & 0 deletions onchain/rollups/.changeset/mean-news-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cartesi/rollups": major
---

Inputs are now blockchain-agnostic and self-contained blobs. For example, inputs added by EVM contracts like `InputBox` contain EVM-specific metadata like `msg.sender` and `block.timestamp`.

Library `LibInput` is removed.
22 changes: 22 additions & 0 deletions onchain/rollups/contracts/common/Inputs.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

/// @title Inputs
/// @notice Defines the signatures of inputs.
interface Inputs {
/// @notice An EVM input.
/// @param sender `msg.sender`
/// @param blockNumber `block.number`
/// @param blockTimestamp `block.timestamp`
/// @param inputIndex The index of the input in the input box
/// @param input The input payload
function EvmInput(
address sender,
uint256 blockNumber,
uint256 blockTimestamp,
uint256 inputIndex,
bytes calldata input
) external;
}
4 changes: 2 additions & 2 deletions onchain/rollups/contracts/inputs/IInputBox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface IInputBox {
/// @param dapp The address of the DApp
/// @param inputIndex The index of the input in the input box
/// @param sender The address that sent the input
/// @param input The contents of the input
/// @param input The input payload
/// @dev MUST be triggered on a successful call to `addInput`.
event InputAdded(
address indexed dapp,
Expand All @@ -20,7 +20,7 @@ interface IInputBox {

/// @notice Add an input to a DApp's input box.
/// @param _dapp The address of the DApp
/// @param _input The contents of the input
/// @param _input The input payload
/// @return The hash of the input plus some extra metadata
/// @dev MUST fire an `InputAdded` event accordingly.
/// Input larger than machine limit will raise `InputSizeExceedsLimit` error.
Expand Down
31 changes: 20 additions & 11 deletions onchain/rollups/contracts/inputs/InputBox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
pragma solidity ^0.8.8;

import {IInputBox} from "./IInputBox.sol";
import {LibInput} from "../library/LibInput.sol";
import {CanonicalMachine} from "../common/CanonicalMachine.sol";
import {Inputs} from "../common/Inputs.sol";

/// @title Input Box
///
/// @notice Trustless and permissionless contract that receives arbitrary blobs
/// (called "inputs") from anyone and adds a compound hash to an append-only list
/// @notice Trustless and permissionless contract that receives arbitrary
/// data from anyone and adds a compound hash to an append-only list
/// (called "input box"). Each DApp has its own input box.
///
/// The hash that is stored on-chain is composed by the hash of the input blob,
/// the block number and timestamp, the input sender address, and the input index.
/// The input hash is composed by the input payload, the block number and timestamp,
/// the address of the input sender, and the index of the input.
///
/// Data availability is guaranteed by the emission of `InputAdded` events
/// on every successful call to `addInput`. This ensures that inputs can be
Expand All @@ -23,6 +24,11 @@ import {LibInput} from "../library/LibInput.sol";
/// From the perspective of this contract, inputs are encoding-agnostic byte
/// arrays. It is up to the DApp to interpret, validate and act upon inputs.
contract InputBox is IInputBox {
using CanonicalMachine for CanonicalMachine.Log2Size;

/// @notice Raised when input is larger than the machine limit.
error InputSizeExceedsLimit();

/// @notice Mapping from DApp address to list of input hashes.
/// @dev See the `getNumberOfInputs`, `getInputHash` and `addInput` functions.
mapping(address => bytes32[]) internal inputBoxes;
Expand All @@ -34,14 +40,17 @@ contract InputBox is IInputBox {
bytes32[] storage inputBox = inputBoxes[_dapp];
uint256 inputIndex = inputBox.length;

bytes32 inputHash = LibInput.computeInputHash(
msg.sender,
block.number,
block.timestamp,
_input,
inputIndex
bytes memory input = abi.encodeCall(
Inputs.EvmInput,
(msg.sender, block.number, block.timestamp, inputIndex, _input)
);

if (input.length > CanonicalMachine.INPUT_MAX_SIZE) {
revert InputSizeExceedsLimit();
}

bytes32 inputHash = keccak256(input);

// add input to the input box
inputBox.push(inputHash);

Expand Down
47 changes: 0 additions & 47 deletions onchain/rollups/contracts/library/LibInput.sol

This file was deleted.

51 changes: 34 additions & 17 deletions onchain/rollups/test/foundry/inputs/InputBox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Test} from "forge-std/Test.sol";
import {InputBox} from "contracts/inputs/InputBox.sol";
import {IInputBox} from "contracts/inputs/IInputBox.sol";
import {CanonicalMachine} from "contracts/common/CanonicalMachine.sol";
import {LibInput} from "contracts/library/LibInput.sol";
import {Inputs} from "contracts/common/Inputs.sol";

contract InputBoxHandler is Test {
IInputBox immutable inputBox;
Expand Down Expand Up @@ -98,12 +98,11 @@ contract InputBoxHandler is Test {
);

// Compute the input hash from the arguments passed to `addInput`
bytes32 computedInputHash = LibInput.computeInputHash(
msg.sender,
block.number,
block.timestamp,
_input,
index
bytes32 computedInputHash = keccak256(
abi.encodeCall(
Inputs.EvmInput,
(msg.sender, block.number, block.timestamp, index, _input)
)
);

// Check if the input hash matches the computed one
Expand Down Expand Up @@ -161,10 +160,12 @@ contract InputBoxTest is Test {
function testAddLargeInput() public {
address dapp = vm.addr(1);

inputBox.addInput(dapp, new bytes(CanonicalMachine.INPUT_MAX_SIZE));
uint256 maxLength = getMaxInputPayloadLength();

vm.expectRevert(LibInput.InputSizeExceedsLimit.selector);
inputBox.addInput(dapp, new bytes(CanonicalMachine.INPUT_MAX_SIZE + 1));
inputBox.addInput(dapp, new bytes(maxLength));

vm.expectRevert(InputBox.InputSizeExceedsLimit.selector);
inputBox.addInput(dapp, new bytes(maxLength + 1));
}

// fuzz testing with multiple inputs
Expand All @@ -175,7 +176,7 @@ contract InputBoxTest is Test {

// assume #bytes for each input is within bounds
for (uint256 i; i < numInputs; ++i) {
vm.assume(_inputs[i].length <= CanonicalMachine.INPUT_MAX_SIZE);
vm.assume(_inputs[i].length <= getMaxInputPayloadLength());
}

// adding inputs
Expand All @@ -199,12 +200,17 @@ contract InputBoxTest is Test {
// testing added inputs
for (uint256 i; i < numInputs; ++i) {
// compute input hash for each input
bytes32 inputHash = LibInput.computeInputHash(
address(this),
i, // block.number
i + year2022, // block.timestamp
_inputs[i],
i // inputBox.length
bytes32 inputHash = keccak256(
abi.encodeCall(
Inputs.EvmInput,
(
address(this),
i, // block.number
i + year2022, // block.timestamp
i, // inputBox.length
_inputs[i]
)
)
);
// test if input hash is the same as in InputBox
assertEq(inputHash, inputBox.getInputHash(_dapp, i));
Expand Down Expand Up @@ -253,4 +259,15 @@ contract InputBoxTest is Test {
}
assertEq(sum, totalNumOfInputs, "total number of inputs");
}

function getMaxInputPayloadLength() internal pure returns (uint256) {
bytes memory blob = abi.encodeCall(
Inputs.EvmInput,
(address(0), 0, 0, 0, new bytes(32))
);
// number of bytes in input blob excluding input payload
uint256 extraBytes = blob.length - 32;
// because it's abi encoded, input payloads are stored as multiples of 32 bytes
return ((CanonicalMachine.INPUT_MAX_SIZE - extraBytes) / 32) * 32;
}
}

0 comments on commit 8e4c677

Please sign in to comment.