From 8e55d38418f218f795d2a0fec4e88164f9d63a5a Mon Sep 17 00:00:00 2001
From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com>
Date: Tue, 21 Nov 2023 17:28:55 +0200
Subject: [PATCH] feat: storage mirror root registry (#10)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

# 🤖 Linear

Closes SAF-30, SAF-31, SAF-32
---
 .../contracts/StorageMirrorRootRegistry.sol   | 69 +++++++++++++++
 solidity/contracts/VerifierModule.sol         |  2 +-
 .../interfaces/IStorageMirrorRootRegistry.sol | 46 +++++++++-
 ...ckOracle.t.sol => BlockHeaderOracle.t.sol} |  0
 .../test/unit/StorageMirrorRootRegistry.t.sol | 88 +++++++++++++++++++
 solidity/test/unit/VerifierModule.t.sol       | 15 ++--
 6 files changed, 210 insertions(+), 10 deletions(-)
 create mode 100644 solidity/contracts/StorageMirrorRootRegistry.sol
 rename solidity/test/unit/{MockOracle.t.sol => BlockHeaderOracle.t.sol} (100%)
 create mode 100644 solidity/test/unit/StorageMirrorRootRegistry.t.sol

diff --git a/solidity/contracts/StorageMirrorRootRegistry.sol b/solidity/contracts/StorageMirrorRootRegistry.sol
new file mode 100644
index 0000000..0bbed98
--- /dev/null
+++ b/solidity/contracts/StorageMirrorRootRegistry.sol
@@ -0,0 +1,69 @@
+// 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;
+
+  /**
+   * @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;
+    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 _accountProof The account proof of the StorageMirror contract in Home chain
+   */
+  function proposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) external {
+    bytes memory _blockHeader = _queryL1BlockHeader();
+
+    (bytes32 _latestVerifiedStorageMirrorStorageRoot, uint256 _blockNumber) =
+      VERIFIER_MODULE.extractStorageMirrorStorageRoot(_blockHeader, _accountProof);
+
+    latestVerifiedStorageMirrorStorageRoot = _latestVerifiedStorageMirrorStorageRoot;
+    latestVerifiedBlockNumber = _blockNumber;
+
+    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/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol
index 80d01e7..f6c7e86 100644
--- a/solidity/contracts/VerifierModule.sol
+++ b/solidity/contracts/VerifierModule.sol
@@ -158,7 +158,7 @@ contract VerifierModule is IVerifierModule {
     IStorageMirror.SafeSettings memory _proposedSettings,
     bytes memory _storageMirrorStorageProof
   ) internal view virtual returns (bytes32 _hashedProposedSettings) {
-    bytes32 _latestStorageRoot = STORAGE_MIRROR_ROOT_REGISTRY.latestVerifiedStorageRoot();
+    bytes32 _latestStorageRoot = STORAGE_MIRROR_ROOT_REGISTRY.latestVerifiedStorageMirrorStorageRoot();
 
     // The slot of where the latest settings hash is stored in the storage mirror
     bytes32 _safeSettingsSlot = keccak256(abi.encode(_safe, _LATEST_VERIFIED_SETTINGS_SLOT));
diff --git a/solidity/interfaces/IStorageMirrorRootRegistry.sol b/solidity/interfaces/IStorageMirrorRootRegistry.sol
index 0f4bc39..165686a 100644
--- a/solidity/interfaces/IStorageMirrorRootRegistry.sol
+++ b/solidity/interfaces/IStorageMirrorRootRegistry.sol
@@ -1,15 +1,57 @@
 // 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 latest verified storage root
+   * @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 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
    */
-  function latestVerifiedStorageRoot() external view returns (bytes32 _latestVerifiedStorageRoot);
+  function latestVerifiedStorageMirrorStorageRoot()
+    external
+    view
+    returns (bytes32 _latestVerifiedStorageMirrorStorageRoot);
 
   /*///////////////////////////////////////////////////////////////
                             LOGIC
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
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'
+    );
+  }
+}
diff --git a/solidity/test/unit/VerifierModule.t.sol b/solidity/test/unit/VerifierModule.t.sol
index 43664a2..b1a79ca 100644
--- a/solidity/test/unit/VerifierModule.t.sol
+++ b/solidity/test/unit/VerifierModule.t.sol
@@ -56,7 +56,8 @@ contract TestVerifierModule is VerifierModule {
     IStorageMirror.SafeSettings memory _proposedSettings,
     bytes memory _storageMirrorStorageProof
   ) public view returns (bytes32 _hashedProposedSettings) {
-    bytes32 _latestStorageRoot = IStorageMirrorRootRegistry(STORAGE_MIRROR_ROOT_REGISTRY).latestVerifiedStorageRoot();
+    bytes32 _latestStorageRoot =
+      IStorageMirrorRootRegistry(STORAGE_MIRROR_ROOT_REGISTRY).latestVerifiedStorageMirrorStorageRoot();
 
     // The slot of where the latest settings hash is stored in the storage mirror
     bytes32 _safeSettingsSlot = keccak256(abi.encode(_safe, 0));
@@ -337,7 +338,7 @@ contract UnitMerklePatriciaTree is Base {
 
     vm.mockCall(
       address(_storageMirrorRegistry),
-      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector),
+      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector),
       abi.encode(_fakeStorageRoot)
     );
 
@@ -380,7 +381,7 @@ contract UnitMerklePatriciaTree is Base {
 
     vm.mockCall(
       address(_storageMirrorRegistry),
-      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector),
+      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector),
       abi.encode(_fakeStorageRoot)
     );
 
@@ -412,7 +413,7 @@ contract UnitMerklePatriciaTree is Base {
 
     vm.mockCall(
       address(_storageMirrorRegistry),
-      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector),
+      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector),
       abi.encode(_fakeStorageRoot)
     );
 
@@ -461,7 +462,7 @@ contract UnitMerklePatriciaTree is Base {
 
     vm.mockCall(
       address(_storageMirrorRegistry),
-      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector),
+      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector),
       abi.encode(_fakeStorageRoot)
     );
 
@@ -563,7 +564,7 @@ contract UnitMerklePatriciaTree is Base {
 
     vm.mockCall(
       address(_storageMirrorRegistry),
-      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector),
+      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector),
       abi.encode(_fakeStorageRoot)
     );
 
@@ -655,7 +656,7 @@ contract UnitMerklePatriciaTree is Base {
 
     vm.mockCall(
       address(_storageMirrorRegistry),
-      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector),
+      abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector),
       abi.encode(_fakeStorageRoot)
     );