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

Standardization of bytes29 libraries #100

Merged
merged 19 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from 17 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
20 changes: 10 additions & 10 deletions packages/contracts/contracts/AttestationCollector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ contract AttestationCollector is GlobalNotaryRegistry, OwnableUpgradeable {
* but different root (meaning one of the attestations is fraudulent),
* we need a system so store all such attestations.
*
* `attestationRoots` stores a list of attested roots for every (domain, nonce) pair
* `attestedRoots` stores a list of attested roots for every (domain, nonce) pair
* `signatures` stores a signature for every submitted (domain, nonce, root) attestation.
* We only store the first submitted signature for such attestation.
*/
// [origin => [nonce => [roots]]]
mapping(uint32 => mapping(uint32 => bytes32[])) internal attestationRoots;
mapping(uint32 => mapping(uint32 => bytes32[])) internal attestedRoots;
// [origin => [nonce => [root => signature]]]
mapping(uint32 => mapping(uint32 => mapping(bytes32 => bytes))) internal signatures;

Expand Down Expand Up @@ -139,8 +139,8 @@ contract AttestationCollector is GlobalNotaryRegistry, OwnableUpgradeable {
uint32 _nonce,
uint256 _index
) public view returns (bytes32) {
require(_index < attestationRoots[_domain][_nonce].length, "!index");
return attestationRoots[_domain][_nonce][_index];
require(_index < attestedRoots[_domain][_nonce].length, "!index");
return attestedRoots[_domain][_nonce][_index];
}

/**
Expand All @@ -149,7 +149,7 @@ contract AttestationCollector is GlobalNotaryRegistry, OwnableUpgradeable {
* If amount > 1, fraud was committed.
*/
function rootsAmount(uint32 _domain, uint32 _nonce) external view returns (uint256) {
return attestationRoots[_domain][_nonce].length;
return attestedRoots[_domain][_nonce].length;
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
Expand Down Expand Up @@ -207,17 +207,17 @@ contract AttestationCollector is GlobalNotaryRegistry, OwnableUpgradeable {
}

function _storeAttestation(address _notary, bytes29 _view) internal returns (bool) {
uint32 domain = _view.attestationDomain();
uint32 nonce = _view.attestationNonce();
bytes32 root = _view.attestationRoot();
uint32 domain = _view.attestedDomain();
uint32 nonce = _view.attestedNonce();
bytes32 root = _view.attestedRoot();
require(nonce > latestNonce[domain][_notary], "Outdated attestation");
// Don't store Attestation, if another Notary
// have submitted the same (domain, nonce, root) before.
if (_signatureExists(domain, nonce, root)) return false;
latestNonce[domain][_notary] = nonce;
latestRoot[domain][_notary] = root;
signatures[domain][nonce][root] = _view.attestationSignature().clone();
attestationRoots[domain][nonce].push(root);
signatures[domain][nonce][root] = _view.notarySignature().clone();
attestedRoots[domain][nonce].push(root);
return true;
}
}
51 changes: 23 additions & 28 deletions packages/contracts/contracts/Destination.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Message } from "./libs/Message.sol";
import { Header } from "./libs/Header.sol";
import { Tips } from "./libs/Tips.sol";
import { TypeCasts } from "./libs/TypeCasts.sol";
import { SystemMessage } from "./system/SystemMessage.sol";
import { SystemMessage } from "./libs/SystemMessage.sol";
import { SystemContract } from "./system/SystemContract.sol";
import { IMessageRecipient } from "./interfaces/IMessageRecipient.sol";
// ============ External Imports ============
Expand Down Expand Up @@ -146,21 +146,16 @@ contract Destination is Version0, SystemContract, GlobalNotaryRegistry, GuardReg
*/
function submitAttestation(bytes memory _attestation) external {
(, bytes29 _view) = _checkNotaryAuth(_attestation);
uint32 remoteDomain = _view.attestationDomain();
uint32 remoteDomain = _view.attestedDomain();
require(remoteDomain != localDomain, "Attestation refers to local chain");
uint32 nonce = _view.attestationNonce();
uint32 nonce = _view.attestedNonce();
MirrorLib.Mirror storage mirror = allMirrors[activeMirrors[remoteDomain]];
require(nonce > mirror.nonce, "Attestation older than current state");
bytes32 newRoot = _view.attestationRoot();
bytes32 newRoot = _view.attestedRoot();
mirror.setConfirmAt(newRoot, block.timestamp);
// update nonce
mirror.setNonce(nonce);
emit AttestationAccepted(
remoteDomain,
nonce,
newRoot,
_view.attestationSignature().clone()
);
emit AttestationAccepted(remoteDomain, nonce, newRoot, _view.notarySignature().clone());
}

/**
Expand Down Expand Up @@ -192,35 +187,35 @@ contract Destination is Version0, SystemContract, GlobalNotaryRegistry, GuardReg
* @param _message Formatted message
*/
function execute(bytes memory _message) public {
bytes29 _m = _message.messageView();
bytes29 _header = _m.header();
uint32 _remoteDomain = _header.origin();
MirrorLib.Mirror storage mirror = allMirrors[activeMirrors[_remoteDomain]];
bytes29 messageView = _message.castToMessage();
bytes29 header = messageView.header();
uint32 remoteDomain = header.origin();
MirrorLib.Mirror storage mirror = allMirrors[activeMirrors[remoteDomain]];
// ensure message was meant for this domain
require(_header.destination() == localDomain, "!destination");
require(header.destination() == localDomain, "!destination");
// ensure message has been proven
bytes32 _messageHash = _m.keccak();
bytes32 _root = mirror.messageStatus[_messageHash];
require(MirrorLib.isPotentialRoot(_root), "!exists || executed");
bytes32 messageHash = messageView.keccak();
bytes32 root = mirror.messageStatus[messageHash];
require(MirrorLib.isPotentialRoot(root), "!exists || executed");
require(
acceptableRoot(_remoteDomain, _header.optimisticSeconds(), _root),
acceptableRoot(remoteDomain, header.optimisticSeconds(), root),
"!optimisticSeconds"
);
// check re-entrancy guard
require(entered == 1, "!reentrant");
entered = 0;
_storeTips(_m.tips());
_storeTips(messageView.tips());
// update message status as executed
mirror.setMessageStatus(_messageHash, MirrorLib.MESSAGE_STATUS_EXECUTED);
address recipient = _checkForSystemMessage(_header.recipient());
mirror.setMessageStatus(messageHash, MirrorLib.MESSAGE_STATUS_EXECUTED);
address recipient = _checkForSystemMessage(header.recipient());
IMessageRecipient(recipient).handle(
_remoteDomain,
_header.nonce(),
_header.sender(),
mirror.confirmAt[_root],
_m.body().clone()
remoteDomain,
header.nonce(),
header.sender(),
mirror.confirmAt[root],
messageView.body().clone()
);
emit Executed(_remoteDomain, _messageHash);
emit Executed(remoteDomain, messageHash);
// reset re-entrancy guard
entered = 1;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/contracts/contracts/Origin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MerkleLib } from "./libs/Merkle.sol";
import { Header } from "./libs/Header.sol";
import { Message } from "./libs/Message.sol";
import { Tips } from "./libs/Tips.sol";
import { SystemMessage } from "./system/SystemMessage.sol";
import { SystemMessage } from "./libs/SystemMessage.sol";
import { SystemContract } from "./system/SystemContract.sol";
import { MerkleTreeManager } from "./Merkle.sol";
import { INotaryManager } from "./interfaces/INotaryManager.sol";
Expand Down Expand Up @@ -208,7 +208,7 @@ contract Origin is
bytes memory _messageBody
) external payable notFailed {
require(_messageBody.length <= MAX_MESSAGE_BODY_BYTES, "msg too long");
require(_tips.tipsView().totalTips() == msg.value, "!tips");
require(_tips.castToTips().totalTips() == msg.value, "!tips");
// get the next nonce for the destination domain, then increment it
nonce = nonce + 1;
bytes32 _sender = _checkForSystemMessage(_recipientAddress);
Expand Down Expand Up @@ -279,8 +279,8 @@ contract Origin is
function improperAttestation(bytes memory _attestation) public notFailed returns (bool) {
// This will revert if signature is not valid
(address _notary, bytes29 _view) = _checkNotaryAuth(_attestation);
uint32 _nonce = _view.attestationNonce();
bytes32 _root = _view.attestationRoot();
uint32 _nonce = _view.attestedNonce();
bytes32 _root = _view.attestedRoot();
// Check if nonce is valid, if not => attestation is fraud
if (_nonce < historicalRoots.length) {
if (_root == historicalRoots[_nonce]) {
Expand Down
55 changes: 43 additions & 12 deletions packages/contracts/contracts/libs/Attestation.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import { SynapseTypes } from "./SynapseTypes.sol";
import { TypedMemView } from "./TypedMemView.sol";

library Attestation {
Expand All @@ -24,8 +25,28 @@ library Attestation {
uint256 internal constant ATTESTATION_DATA_LENGTH = 40;
uint256 internal constant OFFSET_SIGNATURE = ATTESTATION_DATA_LENGTH;

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ MODIFIERS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

modifier onlyAttestation(bytes29 _view) {
_view.assertType(SynapseTypes.ATTESTATION);
_;
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ FORMATTERS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @notice Returns formatted Attestation with provided fields
* @notice Returns a properly typed bytes29 pointer for an attestation payload.
*/
function castToAttestation(bytes memory _payload) internal pure returns (bytes29) {
return _payload.ref(SynapseTypes.ATTESTATION);
}

/**
* @notice Returns a formatted Attestation payload with provided fields
* @param _data Attestation Data (see above)
* @param _signature Notary's signature on `_data`
* @return Formatted attestation
Expand All @@ -39,11 +60,11 @@ library Attestation {
}

/**
* @notice Returns formatted Attestation Data with provided fields
* @notice Returns a formatted AttestationData payload with provided fields
* @param _domain Domain of Origin's chain
* @param _root New merkle root
* @param _nonce Nonce of the merkle root
* @return Formatted data
* @return Formatted attestation data
**/
function formatAttestationData(
uint32 _domain,
Expand All @@ -54,45 +75,55 @@ library Attestation {
}

/**
* @notice Checks that message is an Attestation, by checking its length
* @notice Checks that a payload is a formatted Attestation payload.
*/
function isAttestation(bytes29 _view) internal pure returns (bool) {
// Should have non-zero length for signature. Signature validity is not checked.
// Should have at least 1 bytes for the signature.
// Signature length or validity is not checked, ECDSA.sol takes care of it.
return _view.len() > ATTESTATION_DATA_LENGTH;
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ ATTESTATION SLICING ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @notice Returns domain of chain where the Origin contract is deployed
*/
function attestationDomain(bytes29 _view) internal pure returns (uint32) {
function attestedDomain(bytes29 _view) internal pure onlyAttestation(_view) returns (uint32) {
return uint32(_view.indexUint(OFFSET_ORIGIN_DOMAIN, 4));
}

/**
* @notice Returns nonce of Origin contract at the time, when `root` was the Merkle root.
*/
function attestationNonce(bytes29 _view) internal pure returns (uint32) {
function attestedNonce(bytes29 _view) internal pure onlyAttestation(_view) returns (uint32) {
return uint32(_view.indexUint(OFFSET_NONCE, 4));
}

/**
* @notice Returns a historical Merkle root from the Origin contract
*/
function attestationRoot(bytes29 _view) internal pure returns (bytes32) {
function attestedRoot(bytes29 _view) internal pure onlyAttestation(_view) returns (bytes32) {
return _view.index(OFFSET_ROOT, 32);
}

/**
* @notice Returns Attestation's Data, that is going to be signed by the Notary
*/
function attestationData(bytes29 _view) internal pure returns (bytes29) {
return _view.slice(OFFSET_ORIGIN_DOMAIN, ATTESTATION_DATA_LENGTH, 0);
function attestationData(bytes29 _view) internal pure onlyAttestation(_view) returns (bytes29) {
return
_view.slice(
OFFSET_ORIGIN_DOMAIN,
ATTESTATION_DATA_LENGTH,
SynapseTypes.ATTESTATION_DATA
);
}

/**
* @notice Returns Notary's signature on AttestationData
*/
function attestationSignature(bytes29 _view) internal pure returns (bytes29) {
return _view.slice(OFFSET_SIGNATURE, _view.len() - ATTESTATION_DATA_LENGTH, 0);
function notarySignature(bytes29 _view) internal pure onlyAttestation(_view) returns (bytes29) {
return _view.sliceFrom(OFFSET_SIGNATURE, SynapseTypes.SIGNATURE);
}
}
50 changes: 45 additions & 5 deletions packages/contracts/contracts/libs/Header.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.13;

import { TypedMemView } from "./TypedMemView.sol";
import { TypeCasts } from "./TypeCasts.sol";
import { Message } from "./Message.sol";
import { SynapseTypes } from "./SynapseTypes.sol";

/**
* @notice Library for versioned formatting [the header part]
Expand All @@ -26,18 +26,46 @@ library Header {
* [078 .. 082): optimisticSeconds uint32 4 bytes
*/

uint256 internal constant OFFSET_VERSION = 0;
uint256 internal constant OFFSET_ORIGIN = 2;
uint256 internal constant OFFSET_SENDER = 6;
uint256 internal constant OFFSET_NONCE = 38;
uint256 internal constant OFFSET_DESTINATION = 42;
uint256 internal constant OFFSET_RECIPIENT = 46;
uint256 internal constant OFFSET_OPTIMISTIC_SECONDS = 78;

uint256 internal constant HEADER_LENGTH = 82;

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ MODIFIERS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

modifier onlyHeader(bytes29 _view) {
_view.assertType(Message.HEADER_TYPE);
_view.assertType(SynapseTypes.MESSAGE_HEADER);
_;
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ FORMATTERS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @notice Returns a properly typed bytes29 pointer for a header payload.
*/
function castToHeader(bytes memory _payload) internal pure returns (bytes29) {
return _payload.ref(SynapseTypes.MESSAGE_HEADER);
}

/**
* @notice Returns a formatted Header payload with provided fields
* @param _origin Domain of origin chain
* @param _sender Address that sent the message
* @param _nonce Message nonce on origin chain
* @param _destination Domain of destination chain
* @param _recipient Address that will receive the message
* @param _optimisticSeconds Optimistic period for message execution
* @return Formatted header
**/
function formatHeader(
uint32 _origin,
bytes32 _sender,
Expand All @@ -58,12 +86,24 @@ library Header {
);
}

function headerView(bytes memory _header) internal pure returns (bytes29) {
return _header.ref(Message.HEADER_TYPE);
/**
* @notice Checks that a payload is a formatted Header.
*/
function isHeader(bytes29 _view) internal pure returns (bool) {
uint256 length = _view.len();
// Check if version exists in the payload
if (length < 2) return false;
// Check that header version and its length matches
return headerVersion(_view) == HEADER_VERSION && length == HEADER_LENGTH;
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ HEADER SLICING ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/// @notice Returns header's version field.
function headerVersion(bytes29 _header) internal pure onlyHeader(_header) returns (uint16) {
return uint16(_header.indexUint(0, 2));
return uint16(_header.indexUint(OFFSET_VERSION, 2));
}

/// @notice Returns header's origin field
Expand Down
Loading