From ffa2a264fe747ddcb208651da69d0507e008a372 Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 20 Nov 2023 17:43:10 +0200 Subject: [PATCH 1/3] feat: storage mirror root registry --- solidity/contracts/BlockHeaderOracle.sol | 7 +-- .../contracts/StorageMirrorRootRegistry.sol | 61 ++++++++++++++++++ solidity/interfaces/IBlockHeaderOracle.sol | 47 ++++++++++++++ .../interfaces/IStorageMirrorRootRegistry.sol | 62 +++++++++++++++++++ solidity/interfaces/IVerifierModule.sol | 23 +++++++ ...ckOracle.t.sol => BlockHeaderOracle.t.sol} | 0 6 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 solidity/contracts/StorageMirrorRootRegistry.sol create mode 100644 solidity/interfaces/IBlockHeaderOracle.sol create mode 100644 solidity/interfaces/IStorageMirrorRootRegistry.sol create mode 100644 solidity/interfaces/IVerifierModule.sol rename solidity/test/unit/{MockOracle.t.sol => BlockHeaderOracle.t.sol} (100%) diff --git a/solidity/contracts/BlockHeaderOracle.sol b/solidity/contracts/BlockHeaderOracle.sol index cc082ff..0973d00 100644 --- a/solidity/contracts/BlockHeaderOracle.sol +++ b/solidity/contracts/BlockHeaderOracle.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.19; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; /** * @title BlockHeaderOracle * @notice This contract's purpose is to return the latest stored L1 block header and timestamp * @notice Every X minutes a "magical" off-chain agent provides the latest block header and timestamp */ -contract BlockHeaderOracle { - /** - * @notice Emits when the block header and timestamp are updated - */ - event BlockHeaderUpdated(bytes _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber); +contract BlockHeaderOracle is IBlockHeaderOracle { /** * @notice The block header */ diff --git a/solidity/contracts/StorageMirrorRootRegistry.sol b/solidity/contracts/StorageMirrorRootRegistry.sol new file mode 100644 index 0000000..56bbbd8 --- /dev/null +++ b/solidity/contracts/StorageMirrorRootRegistry.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; + +/** + * @title StorageMirrorRootRegistry + * @notice This contract should accept and store storageRoots of the StorageMirror contract in L1. + */ +contract StorageMirrorRootRegistry is IStorageMirrorRootRegistry { + /** + * @notice The address of the StorageMirror contract in Home chain + */ + address public immutable STORAGE_MIRROR; + + /** + * @notice The address of the Verifier Module + */ + IVerifierModule public immutable VERIFIER_MODULE; + + /** + * @notice The block header oracle + */ + IBlockHeaderOracle public immutable BLOCK_HEADER_ORACLE; + + /** + * @notice The latest verified storage root of the StorageMirror contract in Home chain + */ + bytes32 public latestVerifiedStorageMirrorStorageRoot; + + constructor(address _storageMirror, IVerifierModule _verifierModule, IBlockHeaderOracle _blockHeaderOracle) { + STORAGE_MIRROR = _storageMirror; + VERIFIER_MODULE = _verifierModule; + BLOCK_HEADER_ORACLE = _blockHeaderOracle; + } + + /** + * @notice Users can use to propose and verify a storage root of the StorageMirror contract in Home chain + * @dev Calls queryL1BlockHeader to get the block header of the Home chain + * @dev Call verifier module for the actual verificationn + * @param _blockNumber The block number in the home chain to get the header from + * @param _accountProof The account proof of the StorageMirror contract in Home chain + */ + function proposeAndVerifyStorageMirrorStorageRoot(uint256 _blockNumber, bytes memory _accountProof) external { + bytes memory _blockHeader = _queryL1BlockHeader(); + latestVerifiedStorageMirrorStorageRoot = + VERIFIER_MODULE.extractStorageMirrorStorageRoot(_blockHeader, _accountProof); + + emit VerifiedStorageMirrorStorageRoot(_blockNumber, latestVerifiedStorageMirrorStorageRoot); + } + + /** + * @notice Function that queries an oracle to get the latest bridged block header of the Home chain + * @return _blockHeader The block header of the Home chain + */ + function _queryL1BlockHeader() internal view returns (bytes memory _blockHeader) { + (_blockHeader,) = BLOCK_HEADER_ORACLE.getLatestBlockHeader(); + } +} diff --git a/solidity/interfaces/IBlockHeaderOracle.sol b/solidity/interfaces/IBlockHeaderOracle.sol new file mode 100644 index 0000000..0e175c1 --- /dev/null +++ b/solidity/interfaces/IBlockHeaderOracle.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IBlockHeaderOracle { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emits when the block header and timestamp are updated + */ + event BlockHeaderUpdated(bytes _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The block header + * @return _blockheader The block header + */ + function blockHeader() external view returns (bytes memory _blockheader); + + /** + * @notice The block timestamp of the latest block header + * @return _blockTimestamp The block timestamp + */ + function blockTimestamp() external view returns (uint256 _blockTimestamp); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + /** + * @notice Updates the block header and timestamp + * @param _blockHeader The block header + * @param _blockTimestamp The block timestamp + * @param _blockNumber The block number + */ + function updateBlockHeader(bytes memory _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber) external; + + /** + * @notice Returns the latest block header and timestamp + * @return _blockHeader The block header + * @return _blockTimestamp The block timestamp + */ + function getLatestBlockHeader() external view returns (bytes memory _blockHeader, uint256 _blockTimestamp); +} diff --git a/solidity/interfaces/IStorageMirrorRootRegistry.sol b/solidity/interfaces/IStorageMirrorRootRegistry.sol new file mode 100644 index 0000000..b8d4f03 --- /dev/null +++ b/solidity/interfaces/IStorageMirrorRootRegistry.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; + +interface IStorageMirrorRootRegistry { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emits after the storage root gets verified + * @param _homeChainBlockNumber The block number of the Home chain + * @param _storageRoot The storage root of the StorageMirror contract in Home chain that was verified + */ + event VerifiedStorageMirrorStorageRoot(uint256 indexed _homeChainBlockNumber, bytes32 _storageRoot); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The address of the StorageMirror contract in Home chain + * @return _storageMirror The address of the StorageMirror contract in Home chain + */ + function STORAGE_MIRROR() external view returns (address _storageMirror); + + /** + * @notice The address of the Verifier Module + * @return _verifierModule The address of the Verifier Module + */ + function VERIFIER_MODULE() external view returns (IVerifierModule _verifierModule); + + /** + * @notice The address of the Block Header Oracle + * @return _blockHeaderOracle The address of the Block Header Oracle + */ + function BLOCK_HEADER_ORACLE() external view returns (IBlockHeaderOracle _blockHeaderOracle); + + /** + * @notice The latest verified storage root of the StorageMirror contract in Home chain + * @return _latestVerifiedStorageMirrorStorageRoot The latest verified storage root of the StorageMirror contract in Home chain + */ + function latestVerifiedStorageMirrorStorageRoot() + external + view + returns (bytes32 _latestVerifiedStorageMirrorStorageRoot); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Users can use to propose and verify a storage root of the StorageMirror contract in Home chain + * @dev Calls queryL1BlockHeader to get the block header of the Home chain + * @dev Call verifier module for the actual verificationn + * @param _blockNumber The block number in the home chain to get the header from + * @param _accountProof The account proof of the StorageMirror contract in Home chain + */ + function proposeAndVerifyStorageMirrorStorageRoot(uint256 _blockNumber, bytes memory _accountProof) external; +} diff --git a/solidity/interfaces/IVerifierModule.sol b/solidity/interfaces/IVerifierModule.sol new file mode 100644 index 0000000..d124a23 --- /dev/null +++ b/solidity/interfaces/IVerifierModule.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IVerifierModule { + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The timestamp when the latest settings were verified + * @param _safe The address of the safe + * @return _timestamp The timestamp + */ + function latestVerifiedSettingsTimestamp(address _safe) external view returns (uint256 _timestamp); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + function extractStorageMirrorStorageRoot( + bytes memory _blockHeader, + bytes memory _accountProof + ) external view returns (bytes32 _storageRoot); +} diff --git a/solidity/test/unit/MockOracle.t.sol b/solidity/test/unit/BlockHeaderOracle.t.sol similarity index 100% rename from solidity/test/unit/MockOracle.t.sol rename to solidity/test/unit/BlockHeaderOracle.t.sol From 3c8e46fa0686ad92c267a59fe0b8b228b063f0c3 Mon Sep 17 00:00:00 2001 From: OneTony Date: Tue, 21 Nov 2023 13:21:23 +0200 Subject: [PATCH 2/3] test: unit tests --- .../contracts/StorageMirrorRootRegistry.sol | 8 +- .../interfaces/IStorageMirrorRootRegistry.sol | 3 +- solidity/interfaces/IVerifierModule.sol | 2 +- .../test/unit/StorageMirrorRootRegistry.t.sol | 88 +++++++++++++++++++ 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 solidity/test/unit/StorageMirrorRootRegistry.t.sol diff --git a/solidity/contracts/StorageMirrorRootRegistry.sol b/solidity/contracts/StorageMirrorRootRegistry.sol index 56bbbd8..abccd89 100644 --- a/solidity/contracts/StorageMirrorRootRegistry.sol +++ b/solidity/contracts/StorageMirrorRootRegistry.sol @@ -40,14 +40,16 @@ contract StorageMirrorRootRegistry is IStorageMirrorRootRegistry { * @notice Users can use to propose and verify a storage root of the StorageMirror contract in Home chain * @dev Calls queryL1BlockHeader to get the block header of the Home chain * @dev Call verifier module for the actual verificationn - * @param _blockNumber The block number in the home chain to get the header from * @param _accountProof The account proof of the StorageMirror contract in Home chain */ - function proposeAndVerifyStorageMirrorStorageRoot(uint256 _blockNumber, bytes memory _accountProof) external { + function proposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) external { bytes memory _blockHeader = _queryL1BlockHeader(); - latestVerifiedStorageMirrorStorageRoot = + + (bytes32 _latestVerifiedStorageMirrorStorageRoot, uint256 _blockNumber) = VERIFIER_MODULE.extractStorageMirrorStorageRoot(_blockHeader, _accountProof); + latestVerifiedStorageMirrorStorageRoot = _latestVerifiedStorageMirrorStorageRoot; + emit VerifiedStorageMirrorStorageRoot(_blockNumber, latestVerifiedStorageMirrorStorageRoot); } diff --git a/solidity/interfaces/IStorageMirrorRootRegistry.sol b/solidity/interfaces/IStorageMirrorRootRegistry.sol index b8d4f03..51c0049 100644 --- a/solidity/interfaces/IStorageMirrorRootRegistry.sol +++ b/solidity/interfaces/IStorageMirrorRootRegistry.sol @@ -55,8 +55,7 @@ interface IStorageMirrorRootRegistry { * @notice Users can use to propose and verify a storage root of the StorageMirror contract in Home chain * @dev Calls queryL1BlockHeader to get the block header of the Home chain * @dev Call verifier module for the actual verificationn - * @param _blockNumber The block number in the home chain to get the header from * @param _accountProof The account proof of the StorageMirror contract in Home chain */ - function proposeAndVerifyStorageMirrorStorageRoot(uint256 _blockNumber, bytes memory _accountProof) external; + function proposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) external; } diff --git a/solidity/interfaces/IVerifierModule.sol b/solidity/interfaces/IVerifierModule.sol index d124a23..e030d33 100644 --- a/solidity/interfaces/IVerifierModule.sol +++ b/solidity/interfaces/IVerifierModule.sol @@ -19,5 +19,5 @@ interface IVerifierModule { function extractStorageMirrorStorageRoot( bytes memory _blockHeader, bytes memory _accountProof - ) external view returns (bytes32 _storageRoot); + ) external view returns (bytes32 _storageRoot, uint256 _blockNumber); } diff --git a/solidity/test/unit/StorageMirrorRootRegistry.t.sol b/solidity/test/unit/StorageMirrorRootRegistry.t.sol new file mode 100644 index 0000000..dced08b --- /dev/null +++ b/solidity/test/unit/StorageMirrorRootRegistry.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; +import {StorageMirrorRootRegistry} from 'contracts/StorageMirrorRootRegistry.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; + +contract StorageMirrorRootRegistryForTest is StorageMirrorRootRegistry { + constructor( + address _storageMirror, + IVerifierModule _verifierModule, + IBlockHeaderOracle _blockHeaderOracle + ) StorageMirrorRootRegistry(_storageMirror, _verifierModule, _blockHeaderOracle) {} + + function queryL1BlockHeader() external view returns (bytes memory _blockHeader) { + _blockHeader = _queryL1BlockHeader(); + } +} + +abstract contract Base is Test { + event VerifiedStorageMirrorStorageRoot(uint256 indexed _homeChainBlockNumber, bytes32 _storageRoot); + + address public user; + address public storageMirror; + StorageMirrorRootRegistry public storageMirrorRootRegistry; + StorageMirrorRootRegistryForTest public storageMirrorRootRegistryForTest; + BlockHeaderOracle public blockHeaderOracle; + IVerifierModule public verifierModule; + + function setUp() public { + user = makeAddr('user'); + storageMirror = makeAddr('StorageMirror'); + blockHeaderOracle = new BlockHeaderOracle(); + verifierModule = IVerifierModule(makeAddr('VerifierModule')); + storageMirrorRootRegistry = + new StorageMirrorRootRegistry(storageMirror, verifierModule, IBlockHeaderOracle(blockHeaderOracle)); + storageMirrorRootRegistryForTest = + new StorageMirrorRootRegistryForTest(storageMirror, verifierModule, IBlockHeaderOracle(blockHeaderOracle)); + } +} + +contract UnitStorageMirrorRootRegistryQueryL1BlockHeader is Base { + function testQueryL1BlockHeader(bytes memory _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber) public { + vm.prank(user); + blockHeaderOracle.updateBlockHeader(_blockHeader, _blockTimestamp, _blockNumber); + + vm.expectCall(address(blockHeaderOracle), abi.encodeWithSelector(blockHeaderOracle.getLatestBlockHeader.selector)); + vm.prank(user); + bytes memory _savedBlockHeader = storageMirrorRootRegistryForTest.queryL1BlockHeader(); + + assertEq(_blockHeader, _savedBlockHeader, 'Block header should be saved'); + } +} + +contract UnitStorageMirrorRootRegistryProposeAndVerifyStorageMirrorStorageRoot is Base { + function testProposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) public { + bytes memory _blockHeader = '0x1234'; + uint256 _blockTimestamp = 1234; + uint256 _blockNumber = 1234; + bytes32 _storageRoot = '0x1234'; + + vm.prank(user); + blockHeaderOracle.updateBlockHeader(_blockHeader, _blockTimestamp, _blockNumber); + + vm.mockCall( + address(verifierModule), + abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _blockHeader, _accountProof), + abi.encode(_storageRoot, _blockNumber) + ); + vm.expectCall( + address(verifierModule), + abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _blockHeader, _accountProof) + ); + + vm.expectEmit(true, true, true, true); + emit VerifiedStorageMirrorStorageRoot(_blockNumber, _storageRoot); + + vm.prank(user); + storageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot(_accountProof); + + assertEq( + _storageRoot, storageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot(), 'Storage root should be saved' + ); + } +} From b9ebca1f00fc994c67472cb918169a586e788ad0 Mon Sep 17 00:00:00 2001 From: OneTony Date: Tue, 21 Nov 2023 15:28:22 +0200 Subject: [PATCH 3/3] fix: store latest block number --- solidity/contracts/StorageMirrorRootRegistry.sol | 6 ++++++ solidity/interfaces/IStorageMirrorRootRegistry.sol | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/solidity/contracts/StorageMirrorRootRegistry.sol b/solidity/contracts/StorageMirrorRootRegistry.sol index abccd89..0bbed98 100644 --- a/solidity/contracts/StorageMirrorRootRegistry.sol +++ b/solidity/contracts/StorageMirrorRootRegistry.sol @@ -30,6 +30,11 @@ contract StorageMirrorRootRegistry is IStorageMirrorRootRegistry { */ bytes32 public latestVerifiedStorageMirrorStorageRoot; + /** + * @notice The latest verified block number of the Home chain + */ + uint256 public latestVerifiedBlockNumber; + constructor(address _storageMirror, IVerifierModule _verifierModule, IBlockHeaderOracle _blockHeaderOracle) { STORAGE_MIRROR = _storageMirror; VERIFIER_MODULE = _verifierModule; @@ -49,6 +54,7 @@ contract StorageMirrorRootRegistry is IStorageMirrorRootRegistry { VERIFIER_MODULE.extractStorageMirrorStorageRoot(_blockHeader, _accountProof); latestVerifiedStorageMirrorStorageRoot = _latestVerifiedStorageMirrorStorageRoot; + latestVerifiedBlockNumber = _blockNumber; emit VerifiedStorageMirrorStorageRoot(_blockNumber, latestVerifiedStorageMirrorStorageRoot); } diff --git a/solidity/interfaces/IStorageMirrorRootRegistry.sol b/solidity/interfaces/IStorageMirrorRootRegistry.sol index 51c0049..165686a 100644 --- a/solidity/interfaces/IStorageMirrorRootRegistry.sol +++ b/solidity/interfaces/IStorageMirrorRootRegistry.sol @@ -38,6 +38,12 @@ interface IStorageMirrorRootRegistry { */ function BLOCK_HEADER_ORACLE() external view returns (IBlockHeaderOracle _blockHeaderOracle); + /** + * @notice The latest verified block number of the Home chain + * @return _latestVerifiedBlockNumber The latest verified block number of the Home chain + */ + function latestVerifiedBlockNumber() external view returns (uint256 _latestVerifiedBlockNumber); + /** * @notice The latest verified storage root of the StorageMirror contract in Home chain * @return _latestVerifiedStorageMirrorStorageRoot The latest verified storage root of the StorageMirror contract in Home chain