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) );