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

dapp optimistic period #47

Merged
merged 8 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ jobs:
# Path to your GolangCI-Lint config within the repo (optional)
config: ${{ env.GITHUB_WORKSPACE }}/.golangci.yml
# see: https://github.com/golangci/golangci-lint/issues/2654
args: --timeout=5m
env:
# GitHub token for annotations (optional)
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
93 changes: 40 additions & 53 deletions packages/contracts/contracts/ReplicaManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Version0 } from "./Version0.sol";
import { ReplicaLib } from "./libs/Replica.sol";
import { MerkleLib } from "./libs/Merkle.sol";
import { Message } from "./libs/Message.sol";
import { IMessageRecipient } from "./interfaces/IMessageRecipient.sol";
// ============ External Imports ============
import { TypedMemView } from "./libs/TypedMemView.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
Expand Down Expand Up @@ -79,12 +80,6 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
bytes returnData
);

/**
* @notice Emitted when the value for optimisticTimeout is set
* @param timeout The new value for optimistic timeout
*/
event SetOptimisticTimeout(uint32 indexed remoteDomain, uint32 timeout);

/**
* @notice Emitted when a root's confirmation is modified by governance
* @param root The root for which confirmAt has been set
Expand Down Expand Up @@ -146,19 +141,13 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
* - sets the optimistic timer
* @param _remoteDomain The domain of the Home contract this follows
* @param _updater The EVM id of the updater
* @param _optimisticSeconds The time a new root must wait to be confirmed
*/
function initialize(
uint32 _remoteDomain,
address _updater,
uint32 _optimisticSeconds
) public initializer {
function initialize(uint32 _remoteDomain, address _updater) public initializer {
__Ownable_init();
_setUpdater(_updater);
// set storage variables
entered = 1;
activeReplicas[_remoteDomain] = _createReplica(_remoteDomain, _optimisticSeconds);
emit SetOptimisticTimeout(_remoteDomain, _optimisticSeconds);
activeReplicas[_remoteDomain] = _createReplica(_remoteDomain);
}

// ============ Active Replica Views ============
Expand All @@ -167,10 +156,6 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
return allReplicas[activeReplicas[_remoteDomain]].committedRoot;
}

function activeReplicaOptimisticSeconds(uint32 _remoteDomain) external view returns (uint32) {
return allReplicas[activeReplicas[_remoteDomain]].optimisticSeconds;
}

function activeReplicaConfirmedAt(uint32 _remoteDomain, bytes32 _root)
external
view
Expand All @@ -182,9 +167,9 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
function activeReplicaMessageStatus(uint32 _remoteDomain, bytes32 _messageId)
external
view
returns (ReplicaLib.MessageStatus)
returns (bytes32)
{
return allReplicas[activeReplicas[_remoteDomain]].messages[_messageId];
return allReplicas[activeReplicas[_remoteDomain]].messageStatus[_messageId];
}

// ============ Archived Replica Views ============
Expand Down Expand Up @@ -272,12 +257,14 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
require(_m.destination() == localDomain, "!destination");
// ensure message has been proven
bytes32 _messageHash = _m.keccak();
require(replica.messages[_messageHash] == ReplicaLib.MessageStatus.Proven, "!proven");
bytes32 _root = replica.messageStatus[_messageHash];
require(ReplicaLib.isPotentialRoot(_root), "!exists || processed");
require(acceptableRoot(_remoteDomain, _m.optimisticSeconds(), _root), "!optimisticSeconds");
// check re-entrancy guard
require(entered == 1, "!reentrant");
entered = 0;
// update message status as processed
replica.setMessageStatus(_messageHash, ReplicaLib.MessageStatus.Processed);
replica.setMessageStatus(_messageHash, ReplicaLib.MESSAGE_STATUS_PROCESSED);
// A call running out of gas TYPICALLY errors the whole tx. We want to
// a) ensure the call has a sufficient amount of gas to make a
// meaningful state change.
Expand All @@ -286,6 +273,14 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
// To do this, we require that we have enough gas to process
// and still return. We then delegate only the minimum processing gas.
require(gasleft() >= PROCESS_GAS + RESERVE_GAS, "!gas");
bytes memory _calldata = abi.encodeWithSelector(
IMessageRecipient.handle.selector,
_remoteDomain,
_m.nonce(),
_m.sender(),
replica.confirmAt[_root],
_m.body().clone()
);
// get the message recipient
address _recipient = _m.recipientAddress();
// set up for assembly call
Expand All @@ -294,13 +289,7 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
uint256 _gas = PROCESS_GAS;
// allocate memory for returndata
bytes memory _returnData = new bytes(_maxCopy);
bytes memory _calldata = abi.encodeWithSignature(
"handle(uint32,uint32,bytes32,bytes)",
_m.origin(),
_m.nonce(),
_m.sender(),
_m.body().clone()
);

// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
Expand All @@ -325,6 +314,7 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
if (!_success) revert(_getRevertMsg(_returnData));
// emit process results
emit Process(_remoteDomain, _messageHash, _success, _returnData);
// reset re-entrancy guard
Expand All @@ -333,19 +323,6 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {

// ============ External Owner Functions ============

/**
* @notice Set optimistic timeout period for new roots
* @dev Only callable by owner (Governance)
* @param _optimisticSeconds New optimistic timeout period
*/
function setOptimisticTimeout(uint32 _remoteDomain, uint32 _optimisticSeconds)
external
onlyOwner
{
allReplicas[activeReplicas[_remoteDomain]].setOptimisticTimeout(_optimisticSeconds);
emit SetOptimisticTimeout(_remoteDomain, _optimisticSeconds);
}

/**
* @notice Set Updater role
* @dev MUST ensure that all roots signed by previous Updater have
Expand Down Expand Up @@ -392,7 +369,7 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
if (_time == 0) {
return false;
}
return block.timestamp + _optimisticSeconds >= _time;
return block.timestamp >= _time + _optimisticSeconds;
}

/**
Expand All @@ -413,16 +390,18 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
bytes32[32] calldata _proof,
uint256 _index
) public returns (bool) {
uint32 optimisticSeconds = _message.ref(0).optimisticSeconds();
bytes32 _leaf = keccak256(_message);
ReplicaLib.Replica storage replica = allReplicas[activeReplicas[_remoteDomain]];
// ensure that message has not been proven or processed
require(replica.messages[_leaf] == ReplicaLib.MessageStatus.None, "!MessageStatus.None");
require(
replica.messageStatus[_leaf] == ReplicaLib.MESSAGE_STATUS_NONE,
"!MessageStatus.None"
);
// calculate the expected root based on the proof
bytes32 _calculatedRoot = MerkleLib.branchRoot(_leaf, _proof, _index);
// if the root is valid, change status to Proven
if (acceptableRoot(_remoteDomain, optimisticSeconds, _calculatedRoot)) {
replica.setMessageStatus(_leaf, ReplicaLib.MessageStatus.Processed);
// if the root is valid, save it for later optimistic period checking
if (replica.confirmAt[_calculatedRoot] != 0) {
replica.setMessageStatus(_leaf, _calculatedRoot);
return true;
}
return false;
Expand All @@ -438,12 +417,9 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {

// ============ Internal Functions ============

function _createReplica(uint32 _remoteDomain, uint32 _optimisticSeconds)
internal
returns (uint256 replicaIndex)
{
function _createReplica(uint32 _remoteDomain) internal returns (uint256 replicaIndex) {
replicaIndex = replicaCount;
allReplicas[replicaIndex].setupReplica(_remoteDomain, _optimisticSeconds);
allReplicas[replicaIndex].setupReplica(_remoteDomain);
unchecked {
replicaCount = replicaIndex + 1;
}
Expand All @@ -470,4 +446,15 @@ contract ReplicaManager is Version0, Initializable, OwnableUpgradeable {
/// @notice Hook for potential future use
// solhint-disable-next-line no-empty-blocks
function _beforeUpdate() internal {}

function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) {
// If the _res length is less than 68, then the transaction failed silently (without a revert message)
if (_returnData.length < 68) return "Transaction reverted silently";

assembly {
// Slice the sighash.
_returnData := add(_returnData, 0x04)
}
return abi.decode(_returnData, (string)); // All that remains is the revert string
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface IMessageRecipient {
uint32 _origin,
uint32 _nonce,
bytes32 _sender,
uint256 _rootTimestamp,
bytes memory _message
) external;
}
15 changes: 13 additions & 2 deletions packages/contracts/contracts/libs/Message.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,18 @@ library Message {
uint32 _optimisticSeconds,
bytes memory _body
) internal pure returns (bytes32) {
return keccak256(formatMessage(_origin, _sender, _nonce, _destination, _recipient, _optimisticSeconds, _body));
return
keccak256(
formatMessage(
_origin,
_sender,
_nonce,
_destination,
_recipient,
_optimisticSeconds,
_body
)
);
}

/// @notice Returns message's origin field
Expand Down Expand Up @@ -96,7 +107,7 @@ library Message {
}

/// @notice Returns the optimistic seconds from the message
function optimisticSeconds(bytes29 _message) internal pure returns (uint32){
function optimisticSeconds(bytes29 _message) internal pure returns (uint32) {
return uint32(_message.indexUint(76, 4));
}

Expand Down
36 changes: 19 additions & 17 deletions packages/contracts/contracts/libs/Replica.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,32 @@ library ReplicaLib {
Failed
}

// ============ Constants ============
/// @dev Should not be possible to have 0x0 or 0x1 as valid Merkle root,
/// so it's safe to use those values as NONE/PROCESSED
bytes32 public constant MESSAGE_STATUS_NONE = bytes32(0);
bytes32 public constant MESSAGE_STATUS_PROCESSED = bytes32(uint256(1));

// TODO: optimize read/writes by further packing?
struct Replica {
// The latest root that has been signed by the Updater for this given Replica
bytes32 committedRoot; // 256 bits
// Domain of home chain
uint32 optimisticSeconds; // 32 bits
// Status of Replica based on the Home remote domain
uint32 remoteDomain; // 32 bits
// Optimistic seconds per remote domain (E.g specifies optimistic seconds on a remote domain basis to wait)
// Status of Replica based on the Home remote domain
ReplicaStatus status; // 8 bits
// Mapping of roots to time at which Relayer submitted on-chain. Latency period begins here.
// TODO: confirmAt doesn't need to be uint256 necessarily
mapping(bytes32 => uint256) confirmAt;
// Mapping of message leaves to MessageStatus
mapping(bytes32 => MessageStatus) messages;
// Mapping of message leaves to status:
// - NONE: message not yet submitted
// - PROCESSED: message was proven and processed
// bytes32 root: message was proven against `root`, but not yet processed
mapping(bytes32 => bytes32) messageStatus;
}

function setupReplica(
Replica storage replica,
uint32 _remoteDomain,
uint32 _optimisticSeconds
) internal {
function setupReplica(Replica storage replica, uint32 _remoteDomain) internal {
replica.remoteDomain = _remoteDomain;
replica.optimisticSeconds = _optimisticSeconds;
replica.status = ReplicaStatus.Active;
}

Expand All @@ -67,16 +69,16 @@ library ReplicaLib {
function setMessageStatus(
Replica storage replica,
bytes32 _messageHash,
MessageStatus _status
bytes32 _status
) internal {
replica.messages[_messageHash] = _status;
}

function setOptimisticTimeout(Replica storage replica, uint32 _optimisticSeconds) internal {
replica.optimisticSeconds = _optimisticSeconds;
replica.messageStatus[_messageHash] = _status;
}

function setStatus(Replica storage replica, ReplicaStatus _status) internal {
replica.status = _status;
}

function isPotentialRoot(bytes32 messageStatus) internal pure returns (bool) {
return messageStatus != MESSAGE_STATUS_NONE && messageStatus != MESSAGE_STATUS_PROCESSED;
}
}
16 changes: 4 additions & 12 deletions packages/contracts/test/Replica.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@ contract ReplicaTest is SynapseTest {
using ReplicaLib for ReplicaLib.Replica;

ReplicaLib.Replica replica;
uint32 optimisticSeconds;

function setUp() public override {
super.setUp();
optimisticSeconds = 10;
replica.setupReplica(remoteDomain, optimisticSeconds);
replica.setupReplica(remoteDomain);
}

function test_setup() public {
assertEq(replica.committedRoot, bytes32(""));
assertEq(replica.remoteDomain, remoteDomain);
assertEq(replica.optimisticSeconds, optimisticSeconds);
assertEq(uint256(replica.status), 1);
}

Expand All @@ -36,14 +33,9 @@ contract ReplicaTest is SynapseTest {
assertEq(replica.confirmAt[_committedRoot], _confirmAt);
}

function test_setMessageStatus(bytes32 _messageHash) public {
replica.setMessageStatus(_messageHash, ReplicaLib.MessageStatus.Processed);
assertEq(uint256(replica.messages[_messageHash]), 2);
}

function test_setOptimisticTimeout(uint32 _optimisticSeconds) public {
replica.setOptimisticTimeout(_optimisticSeconds);
assertEq(replica.optimisticSeconds, _optimisticSeconds);
function test_setMessageStatus(bytes32 _messageHash, bytes32 _status) public {
replica.setMessageStatus(_messageHash, _status);
assertEq(replica.messageStatus[_messageHash], _status);
}

function test_setStatus() public {
Expand Down
Loading