diff --git a/da-contracts/contracts/da-layers/eigenda/DummyEigenDABridge.sol b/da-contracts/contracts/da-layers/eigenda/DummyEigenDABridge.sol new file mode 100644 index 000000000..6cb5cc986 --- /dev/null +++ b/da-contracts/contracts/da-layers/eigenda/DummyEigenDABridge.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IEigenDABridge} from "./IEigenDABridge.sol"; +import {IImplementation} from "./IImplementation.sol"; +import {DummyImplementation} from "./DummyImplementation.sol"; + +contract DummyEigenDABridge is IEigenDABridge { + IImplementation public implementationContract; + + constructor() { + implementationContract = new DummyImplementation(); + } + + function implementation() external view returns (IImplementation) { + return implementationContract; + } + + function verifyBlobLeaf(bytes calldata) external view returns (bool) { + return true; + } +} diff --git a/da-contracts/contracts/da-layers/eigenda/DummyImplementation.sol b/da-contracts/contracts/da-layers/eigenda/DummyImplementation.sol new file mode 100644 index 000000000..6be819b13 --- /dev/null +++ b/da-contracts/contracts/da-layers/eigenda/DummyImplementation.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IImplementation} from "./IImplementation.sol"; + +contract DummyImplementation is IImplementation { + function rangeStartBlocks(bytes32) external view returns (uint32 startBlock) { + return 1; + } +} diff --git a/da-contracts/contracts/da-layers/eigenda/EigenDAAttestationLib.sol b/da-contracts/contracts/da-layers/eigenda/EigenDAAttestationLib.sol new file mode 100644 index 000000000..4399ac1c6 --- /dev/null +++ b/da-contracts/contracts/da-layers/eigenda/EigenDAAttestationLib.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +import {IEigenDABridge} from "./IEigenDABridge.sol"; +import {IImplementation} from "./IImplementation.sol"; + +abstract contract EigenDAAttestationLib { + struct AttestationData { + uint32 blockNumber; + uint128 leafIndex; + } + + IEigenDABridge public bridge; + IImplementation public implementation; + + /// @dev Mapping from attestation leaf to attestation data. + /// It is necessary for recovery of the state from the onchain data. + mapping(bytes32 => AttestationData) public attestations; + + error InvalidAttestationProof(); + + constructor(IEigenDABridge _bridge) { + bridge = _bridge; + implementation = bridge.implementation(); + } + + function _attest(bytes memory input) internal virtual { + if (!bridge.verifyBlobLeaf(input)) revert InvalidAttestationProof(); + /*attestations[input.leaf] = AttestationData( + implementation.rangeStartBlocks(input.rangeHash) + uint32(input.dataRootIndex) + 1, + uint128(input.leafIndex) + );*/ + } +} diff --git a/da-contracts/contracts/da-layers/eigenda/EigenDAL1Validator.sol b/da-contracts/contracts/da-layers/eigenda/EigenDAL1Validator.sol new file mode 100644 index 000000000..cb9b40295 --- /dev/null +++ b/da-contracts/contracts/da-layers/eigenda/EigenDAL1Validator.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1DAValidator, L1DAValidatorOutput} from "../../IL1DAValidator.sol"; +import {ValL1DAWrongInputLength} from "../../DAContractsErrors.sol"; +import {EigenDAAttestationLib} from "./EigenDAAttestationLib.sol"; +import {IEigenDABridge} from "./IEigenDABridge.sol"; + +contract EigenDAL1Validator is IL1DAValidator, EigenDAAttestationLib { + error InvalidValidatorOutputHash(); + + constructor(IEigenDABridge _eigendaBridge) EigenDAAttestationLib(_eigendaBridge) {} + + function checkDA( + uint256, // _chainId + uint256, // _batchNumber + bytes32 l2DAValidatorOutputHash, // TODO: Maybe we don't need this + bytes calldata operatorDAInput, + uint256 maxBlobsSupported + ) external override returns (L1DAValidatorOutput memory output) { + // TODO: Implement real validation logic. + // For Validiums, we expect the operator to just provide the data for us. + // We don't need to do any checks with regard to the l2DAValidatorOutputHash. + if (operatorDAInput.length < 32) { + revert ValL1DAWrongInputLength(operatorDAInput.length, 32); + } + bytes32 stateDiffHash = abi.decode(operatorDAInput[:32], (bytes32)); + + output.stateDiffHash = stateDiffHash; + + /*IEigenDABridge.MerkleProofInput memory input = abi.decode(operatorDAInput[32:], (IEigenDABridge.MerkleProofInput)); + if (l2DAValidatorOutputHash != keccak256(abi.encodePacked(output.stateDiffHash, input.leaf))) + revert InvalidValidatorOutputHash();*/ //TODO: Maybe we don't need this + _attest(operatorDAInput[32:]); + + output.blobsLinearHashes = new bytes32[](maxBlobsSupported); + output.blobsOpeningCommitments = new bytes32[](maxBlobsSupported); + } +} diff --git a/da-contracts/contracts/da-layers/eigenda/IEigenDABridge.sol b/da-contracts/contracts/da-layers/eigenda/IEigenDABridge.sol new file mode 100644 index 000000000..bfcd3d300 --- /dev/null +++ b/da-contracts/contracts/da-layers/eigenda/IEigenDABridge.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +import {IImplementation} from "./IImplementation.sol"; + +interface IEigenDABridge { + // solhint-disable-next-line gas-struct-packing + struct Message { + // single-byte prefix representing the message type + bytes1 messageType; + // address of message sender + bytes32 from; + // address of message receiver + bytes32 to; + // origin chain code + uint32 originDomain; + // destination chain code + uint32 destinationDomain; + // data being sent + bytes data; + // nonce + uint64 messageId; + } + + struct MerkleProofInput { + // proof of inclusion for the data root + bytes32[] dataRootProof; + // proof of inclusion of leaf within blob/bridge root + bytes32[] leafProof; + // abi.encodePacked(startBlock, endBlock) of header range commitment on implementation + bytes32 rangeHash; + // index of the data root in the commitment tree + uint256 dataRootIndex; + // blob root to check proof against, or reconstruct the data root + bytes32 blobRoot; + // bridge root to check proof against, or reconstruct the data root + bytes32 bridgeRoot; + // leaf being proven + bytes32 leaf; + // index of the leaf in the blob/bridge root tree + uint256 leafIndex; + } + + function implementation() external view returns (IImplementation implementation); + function verifyBlobLeaf(bytes calldata input) external view returns (bool); +} diff --git a/da-contracts/contracts/da-layers/eigenda/IImplementation.sol b/da-contracts/contracts/da-layers/eigenda/IImplementation.sol new file mode 100644 index 000000000..7ea954ebd --- /dev/null +++ b/da-contracts/contracts/da-layers/eigenda/IImplementation.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +interface IImplementation { + function rangeStartBlocks(bytes32 rangeHash) external view returns (uint32 startBlock); +} diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 5dd225453..0d8378ed1 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -176,6 +176,15 @@ contract DeployL1Script is Script, DeployUtils { ); } + function getEigenDAL2ValidatorAddress() internal returns (address) { + return + Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readEigenDAL2ValidatorBytecode()), + hex"" + ); + } + function deployDAValidators() internal { vm.broadcast(msg.sender); address rollupDAManager = address(new RollupDAManager()); @@ -202,6 +211,17 @@ contract DeployL1Script is Script, DeployUtils { addresses.daAddresses.availL1DAValidator = config.contracts.availL1DAValidator; } + if (config.contracts.eigenDAL1Validator == address(0)) { + address eigendaBridge = deployViaCreate2(Utils.readDummyEigenDABridgeBytecode(), ""); + addresses.daAddresses.eigenDAL1Validator = deployViaCreate2( + Utils.readEigenDAL1ValidatorBytecode(), + abi.encode(eigendaBridge) + ); + console.log("EigenDAL1Validator deployed at:", addresses.daAddresses.eigenDAL1Validator); + } else { + addresses.daAddresses.eigenDAL1Validator = config.contracts.eigenDAL1Validator; + } + vm.startBroadcast(msg.sender); RollupDAManager(rollupDAManager).updateDAPair(address(rollupDAValidator), getRollupL2ValidatorAddress(), true); vm.stopBroadcast(); @@ -707,6 +727,11 @@ contract DeployL1Script is Script, DeployUtils { "avail_l1_da_validator_addr", addresses.daAddresses.availL1DAValidator ); + vm.serializeAddress( + "deployed_addresses", + "eigenda_l1_validator_addr", + addresses.daAddresses.eigenDAL1Validator + ); string memory deployedAddresses = vm.serializeAddress( "deployed_addresses", @@ -725,6 +750,7 @@ contract DeployL1Script is Script, DeployUtils { vm.serializeAddress("root", "expected_rollup_l2_da_validator_addr", getRollupL2ValidatorAddress()); vm.serializeAddress("root", "expected_no_da_validium_l2_validator_addr", getNoDAValidiumL2ValidatorAddress()); vm.serializeAddress("root", "expected_avail_l2_da_validator_addr", getAvailL2ValidatorAddress()); + vm.serializeAddress("root", "expected_eigenda_l2_validator_addr", getEigenDAL2ValidatorAddress()); string memory toml = vm.serializeAddress("root", "owner_address", config.ownerAddress); vm.writeToml(toml, outputPath); diff --git a/l1-contracts/deploy-scripts/DeployL2Contracts.sol b/l1-contracts/deploy-scripts/DeployL2Contracts.sol index fab4e3dd0..d5f4529db 100644 --- a/l1-contracts/deploy-scripts/DeployL2Contracts.sol +++ b/l1-contracts/deploy-scripts/DeployL2Contracts.sol @@ -170,7 +170,7 @@ contract DeployL2Script is Script { } else if (config.validatorType == DAValidatorType.Avail) { bytecode = L2ContractsBytecodesLib.readAvailL2DAValidatorBytecode(); } else if (config.validatorType == DAValidatorType.EigenDA) { - bytecode = L2ContractsBytecodesLib.readNoDAL2DAValidatorBytecode(); // TODO: update to EigenDA + bytecode = L2ContractsBytecodesLib.readEigenDAL2ValidatorBytecode(); } else { revert("Invalid DA validator type"); } diff --git a/l1-contracts/deploy-scripts/DeployUtils.s.sol b/l1-contracts/deploy-scripts/DeployUtils.s.sol index 44c56ce42..e79516904 100644 --- a/l1-contracts/deploy-scripts/DeployUtils.s.sol +++ b/l1-contracts/deploy-scripts/DeployUtils.s.sol @@ -102,6 +102,7 @@ struct DataAvailabilityDeployedAddresses { address l1RollupDAValidator; address noDAValidiumL1DAValidator; address availL1DAValidator; + address eigenDAL1Validator; } // solhint-disable-next-line gas-struct-packing @@ -165,6 +166,7 @@ struct ContractsConfig { bytes32 bootloaderHash; bytes32 defaultAAHash; address availL1DAValidator; + address eigenDAL1Validator; } struct TokensConfig { @@ -240,6 +242,10 @@ contract DeployUtils is Script { config.contracts.availL1DAValidator = toml.readAddress("$.contracts.avail_l1_da_validator"); } + if (vm.keyExistsToml(toml, "$.contracts.eigenda_l1_validator")) { + config.contracts.eigenDAL1Validator = toml.readAddress("$.contracts.eigenda_l1_validator"); + } + config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); } diff --git a/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol b/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol index e0644d220..51914dba3 100644 --- a/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol +++ b/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol @@ -82,6 +82,12 @@ library L2ContractsBytecodesLib { return Utils.readZKFoundryBytecodeL2("AvailL2DAValidator.sol", "AvailL2DAValidator"); } + /// @notice Reads the bytecode of the ValidiumL2DAValidator contract for EigenDA. + /// @return The bytecode of the ValidiumL2DAValidator contract. + function readEigenDAL2ValidatorBytecode() internal view returns (bytes memory) { + return Utils.readZKFoundryBytecodeL2("EigenDAL2Validator.sol", "EigenDAL2Validator"); + } + /// @notice Reads the bytecode of the ValidiumL2DAValidator contract for NoDA validium. /// @return The bytecode of the ValidiumL2DAValidator contract. function readNoDAL2DAValidatorBytecode() internal view returns (bytes memory) { diff --git a/l1-contracts/deploy-scripts/Utils.sol b/l1-contracts/deploy-scripts/Utils.sol index a67341785..6e331b25c 100644 --- a/l1-contracts/deploy-scripts/Utils.sol +++ b/l1-contracts/deploy-scripts/Utils.sol @@ -1031,6 +1031,14 @@ library Utils { bytecode = readFoundryBytecode("/../da-contracts/out/DummyAvailBridge.sol/DummyAvailBridge.json"); } + function readDummyEigenDABridgeBytecode() internal view returns (bytes memory bytecode) { + bytecode = readFoundryBytecode("/../da-contracts/out/DummyEigenDABridge.sol/DummyEigenDABridge.json"); + } + + function readEigenDAL1ValidatorBytecode() internal view returns (bytes memory bytecode) { + bytecode = readFoundryBytecode("/../da-contracts/out/EigenDAL1Validator.sol/EigenDAL1Validator.json"); + } + // add this to be excluded from coverage report function test() internal {} } diff --git a/l2-contracts/contracts/data-availability/EigenDAL2Validator.sol b/l2-contracts/contracts/data-availability/EigenDAL2Validator.sol new file mode 100644 index 000000000..90047cd54 --- /dev/null +++ b/l2-contracts/contracts/data-availability/EigenDAL2Validator.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; +import {StateDiffL2DAValidator} from "./StateDiffL2DAValidator.sol"; + +/// Rollup DA validator. It will publish data that would allow to use either calldata or blobs. +contract EigenDAL2Validator is IL2DAValidator, StateDiffL2DAValidator { + function validatePubdata( + // The rolling hash of the user L2->L1 logs. + bytes32, + // The root hash of the user L2->L1 logs. + bytes32, + // The chained hash of the L2->L1 messages + bytes32 _chainedMessagesHash, + // The chained hash of uncompressed bytecodes sent to L1 + bytes32 _chainedBytecodesHash, + // Operator data, that is related to the DA itself + bytes calldata _totalL2ToL1PubdataAndStateDiffs + ) external returns (bytes32 outputHash) { + (bytes32 stateDiffHash, bytes calldata _totalPubdata, ) = _produceStateDiffPubdata( + _chainedMessagesHash, + _chainedBytecodesHash, + _totalL2ToL1PubdataAndStateDiffs + ); + + bytes32 fullPubdataHash = keccak256(_totalPubdata); + outputHash = keccak256(abi.encodePacked(stateDiffHash, fullPubdataHash)); + } +}