Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimistic roots smart contracts #3969

Merged
merged 8 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
368 changes: 359 additions & 9 deletions packages/deployments/contracts/contracts/messaging/RootManager.sol

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {TypeCasts} from "../../shared/libraries/TypeCasts.sol";
import {MerkleLib} from "../libraries/MerkleLib.sol";
import {Message} from "../libraries/Message.sol";
import {RateLimited} from "../libraries/RateLimited.sol";
import {SnapshotId} from "../libraries/SnapshotId.sol";

import {MerkleTreeManager} from "../MerkleTreeManager.sol";
import {WatcherClient} from "../WatcherClient.sol";
Expand Down Expand Up @@ -54,6 +55,8 @@ abstract contract SpokeConnector is Connector, ConnectorManager, WatcherClient,

event DelayBlocksUpdated(uint256 indexed updated, address caller);

event SnapshotRootSaved(uint256 indexed snapshotId, bytes32 indexed root, uint256 indexed count);

/**
* @notice Emitted when funds are withdrawn by the admin
* @dev See comments in `withdrawFunds`
Expand Down Expand Up @@ -161,6 +164,11 @@ abstract contract SpokeConnector is Connector, ConnectorManager, WatcherClient,
*/
mapping(bytes32 => MessageStatus) public messages;

/**
* @notice Mapping of the snapshot roots for a specific index. Used for data availability for off-chain scripts
*/
mapping(uint256 => bytes32) public snapshotRoots;

// ============ Modifiers ============

modifier onlyAllowlistedSender() {
Expand Down Expand Up @@ -323,6 +331,9 @@ abstract contract SpokeConnector is Connector, ConnectorManager, WatcherClient,
* @dev The root of this tree will eventually be dispatched to mainnet via `send`. On mainnet (the "hub"),
* it will be combined into a single aggregate root by RootManager (along with outbound roots from other
* chains). This aggregate root will be redistributed to all destination chains.
* @dev This function is also in charge of saving the snapshot root when needed. If the message being added to the
* tree is the first of the current period this means the last snapshot finished and its root must be saved. The saving
* happens before adding the new message to the tree.
*
* NOTE: okay to leave dispatch operational when paused as pause is designed for crosschain interactions
*/
Expand All @@ -331,6 +342,15 @@ abstract contract SpokeConnector is Connector, ConnectorManager, WatcherClient,
bytes32 _recipientAddress,
bytes memory _messageBody
) external onlyAllowlistedSender returns (bytes32, bytes memory) {
// Before inserting the new message to the tree we need to check if the last snapshot root must be calculated and set.
uint256 _lastCompletedSnapshotId = SnapshotId.getLastCompletedSnapshotId();
if (snapshotRoots[_lastCompletedSnapshotId] == 0) {
// Saves current tree root as last snapshot root before adding the new message.
bytes32 _currentRoot = MERKLE.root();
snapshotRoots[_lastCompletedSnapshotId] = _currentRoot;
emit SnapshotRootSaved(_lastCompletedSnapshotId, _currentRoot, MERKLE.count());
}

// Get the next nonce for the destination domain, then increment it.
uint32 _nonce = nonces[_destinationDomain]++;

Expand Down Expand Up @@ -432,6 +452,15 @@ abstract contract SpokeConnector is Connector, ConnectorManager, WatcherClient,
}
}

/**
* @notice This function gets the last completed snapshot id
* @dev The value is calculated through an internal function to reuse code and save gas
* @return _lastCompletedSnapshotId The last completed snapshot id
*/
function getLastCompletedSnapshotId() external view returns (uint256 _lastCompletedSnapshotId) {
_lastCompletedSnapshotId = SnapshotId.getLastCompletedSnapshotId();
}

// ============ Private Functions ============

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

/**
* @title SnapshotId library
* @notice A library to be used in spoke connector and root manager to calculates the current snapshot id
*/
library SnapshotId {
/**
* @notice Duration of the snapshot
* @dev Off-chain agents could change the effective snapshot length by skipping snapshots. This is the
* smallest unit of snapshot duration, not just the only option.
*/
uint256 public constant SNAPSHOT_DURATION = 30 minutes;

/**
* @notice This function calculates the last completed snapshot id
* @return _lastCompletedSnapshotId The last completed snapshot id
*/
function getLastCompletedSnapshotId() internal view returns (uint256 _lastCompletedSnapshotId) {
unchecked {
_lastCompletedSnapshotId = block.timestamp / SNAPSHOT_DURATION;
}
}

/**
* @notice Get the snapshot duration
* @return _snapshotDuration The snapshot duration
*/
function getSnapshotDuration() internal pure returns (uint256 _snapshotDuration) {
_snapshotDuration = SNAPSHOT_DURATION;
}
gotzenx marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,18 @@ contract PingPong is ConnectorHelper {

MockSpokeConnector(payable(_destinationConnectors.spoke)).setUpdatesAggregate(true);

// enroll this as approved watcher to activate slowmode
WatcherManager(_watcherManager).addWatcher(address(this));
// check setup
assertTrue(WatcherManager(_watcherManager).isWatcher(address(this)));

// configure root manager with connectors
RootManager(_rootManager).addConnector(_originDomain, _originConnectors.hub);
RootManager(_rootManager).addConnector(_destinationDomain, _destinationConnectors.hub);
// set root manager to slow mode
RootManager(_rootManager).activateSlowMode();
// check setup
assertFalse(RootManager(_rootManager).optimisticMode());
assertEq(RootManager(_rootManager).connectors(0), _originConnectors.hub);
assertEq(RootManager(_rootManager).connectors(1), _destinationConnectors.hub);
assertEq(RootManager(_rootManager).domains(0), _originDomain);
Expand Down Expand Up @@ -317,11 +325,7 @@ contract PingPong is ConnectorHelper {
}

// Process a given aggregateRoot on a given spoke.
function utils_processAggregateRootAndAssert(
address connector,
address amb,
bytes32 aggregateRoot
) public {
function utils_processAggregateRootAndAssert(address connector, address amb, bytes32 aggregateRoot) public {
// Expect MessageProcessed on the target spoke.
vm.expectEmit(true, true, true, true);
emit MessageProcessed(abi.encode(aggregateRoot), amb);
Expand Down
Loading