Skip to content

Commit

Permalink
feat: Report library
Browse files Browse the repository at this point in the history
  • Loading branch information
ChiTimesChi committed Aug 17, 2022
1 parent 7df8fe1 commit dbe6922
Show file tree
Hide file tree
Showing 2 changed files with 394 additions and 0 deletions.
247 changes: 247 additions & 0 deletions packages/contracts/contracts/libs/Report.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

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

/**
* @notice Library for formatting the Guard Reports.
* Reports are submitted to Home contracts in order to slash a fraudulent Notary.
* Reports are submitted to ReplicaManager contracts in order to blacklist
* an allegedly fraudulent Notary.
* Just like an Attestation, a Report could be checked on Home contract
* on the chain the reported Notary is attesting.
* Report includes:
* - Flag, indicating whether the reported attestation is fraudulent.
* - Reported Attestation (Attestation data and Notary signature on that data).
* - Guard signature on Report data.
*/
library Report {
using Attestation for bytes;
using Attestation for bytes29;
using TypedMemView for bytes;
using TypedMemView for bytes29;

/**
* @dev More flag values could be added in the future,
* e.g. flag indicating "type" of fraud.
* Going forward, Flag.Valid is guaranteed to be
* the only Flag specifying a valid attestation.
*
* Flag.Valid indicates a reported valid Attestation.
* Flag.Fraud indicates a reported fraud Attestation.
*/
enum Flag {
Valid,
Fraud
}

/**
* @dev ReportData memory layout
* [000 .. 001): flag uint8 1 bytes
* [001 .. 041): attData bytes 40 bytes
*
* guardSig is Guard's signature on ReportData
*
* Report memory layout
* [000 .. 002): attLength uint16 2 bytes (length == AAA - 3)
* [002 .. 003): flag uint8 1 bytes
* [003 .. AAA]: attestation bytes ?? bytes (40 + 64/65 bytes)
* [AAA .. END): guardSig bytes ?? bytes (64/65 bytes)
*
* Unpack attestation field (see Attestation.sol)
* [000 .. 002): attLength uint16 2 bytes (length == AAA - 3)
* [002 .. 003): flag uint8 1 bytes
* [003 .. 043]: attData bytes 40 bytes
* [043 .. AAA): notarySig bytes ?? bytes (64/65 bytes)
* [AAA .. END): guardSig bytes ?? bytes (64/65 bytes)
*
* notarySig is Notary's signature on AttestationData
*
* flag + attData = reportData (see above), so
*
* Report memory layout (sliced alternatively)
* [000 .. 002): attLength uint16 2 bytes (length == AAA - 3)
* [002 .. 043): reportData bytes 41 bytes
* [043 .. AAA): notarySig bytes ?? bytes (64/65 bytes)
* [AAA .. END): guardSig bytes ?? bytes (64/65 bytes)
*/

uint256 internal constant OFFSET_ATTESTATION_LENGTH = 0;
uint256 internal constant OFFSET_FLAG = 2;
uint256 internal constant OFFSET_ATTESTATION = 3;

uint256 internal constant ATTESTATION_DATA_LENGTH = 40;
uint256 internal constant REPORT_DATA_LENGTH = 1 + ATTESTATION_DATA_LENGTH;

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

modifier onlyReport(bytes29 _view) {
_view.assertType(SynapseTypes.REPORT);
_;
}

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

/**
* @notice Returns formatted report data with provided fields
* @param _flag Flag indicating whether attestation is fraudulent
* @param _attestation Formatted attestation (see Attestation.sol)
* @return Formatted report data
**/
function formatReportData(Flag _flag, bytes memory _attestation)
internal
view
returns (bytes memory)
{
// Extract attestation data from payload
bytes memory attestationData = _attestation.castToAttestation().attestationData().clone();
// Construct report data
return abi.encodePacked(uint8(_flag), attestationData);
}

/**
* @notice Returns formatted report data on valid attestation with provided fields
* @param _validAttestation Formatted attestation (see Attestation.sol)
* @return Formatted report data
**/
function formatValidReportData(bytes memory _validAttestation)
internal
view
returns (bytes memory)
{
return formatReportData(Flag.Valid, _validAttestation);
}

/**
* @notice Returns formatted report data on fraud attestation with provided fields
* @param _fraudAttestation Formatted attestation (see Attestation.sol)
* @return Formatted report data
**/
function formatFraudReportData(bytes memory _fraudAttestation)
internal
view
returns (bytes memory)
{
return formatReportData(Flag.Fraud, _fraudAttestation);
}

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

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

/**
* @notice Returns formatted report payload with provided fields.
* @param _flag Flag indicating whether attestation is fraudulent
* @param _attestation Formatted attestation (see Attestation.sol)
* @param _guardSig Guard signature on reportData (see formatReportData below)
* @return Formatted report
**/
function formatReport(
Flag _flag,
bytes memory _attestation,
bytes memory _guardSig
) internal pure returns (bytes memory) {
return abi.encodePacked(uint16(_attestation.length), uint8(_flag), _attestation, _guardSig);
}

/**
* @notice Returns formatted report payload on a valid attestation with provided fields.
* @param _validAttestation Formatted attestation (see Attestation.sol)
* @param _guardSig Guard signature on reportData (see ReportData section above)
* @return Formatted report
**/
function formatValidReport(bytes memory _validAttestation, bytes memory _guardSig)
internal
pure
returns (bytes memory)
{
return formatReport(Flag.Valid, _validAttestation, _guardSig);
}

/**
* @notice Returns formatted report payload on a fraud attestation with provided fields.
* @param _fraudAttestation Formatted attestation (see Attestation.sol)
* @param _guardSig Guard signature on reportData (see ReportData section above)
* @return Formatted report
**/
function formatFraudReport(bytes memory _fraudAttestation, bytes memory _guardSig)
internal
pure
returns (bytes memory)
{
return formatReport(Flag.Fraud, _fraudAttestation, _guardSig);
}

/**
* @notice Checks that a payload is a formatted Report payload.
*/
function isReport(bytes29 _view) internal pure returns (bool) {
uint256 length = _view.len();
// Attestation length & flag should exist
if (length < OFFSET_ATTESTATION) return false;
uint256 attestationLength = _attestationLength(_view);
// Guard signature needs to exist
if (length <= OFFSET_ATTESTATION + attestationLength) return false;
// Attestation needs to be formatted as well
return reportedAttestation(_view).isAttestation();
}

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

/**
* @notice Returns whether Report's Flag is Fraud (indicating fraudulent attestation).
*/
function reportedFraud(bytes29 _view) internal pure onlyReport(_view) returns (bool) {
return _view.indexUint(OFFSET_FLAG, 1) != uint8(Flag.Valid);
}

/**
* @notice Returns Report's Attestation (which is supposed to be signed by the Notary already).
*/
function reportedAttestation(bytes29 _view) internal pure onlyReport(_view) returns (bytes29) {
return _view.slice(OFFSET_ATTESTATION, _attestationLength(_view), SynapseTypes.ATTESTATION);
}

/**
* @notice Returns Report's Data, that is going to be signed by the Guard.
*/
function reportData(bytes29 _view) internal pure onlyReport(_view) returns (bytes29) {
// reportData starts from Flag
return _view.slice(OFFSET_FLAG, REPORT_DATA_LENGTH, SynapseTypes.REPORT_DATA);
}

/**
* @notice Returns Guard's signature on ReportData.
*/
function guardSignature(bytes29 _view) internal pure onlyReport(_view) returns (bytes29) {
uint256 offsetSignature = OFFSET_ATTESTATION + _attestationLength(_view);
return _view.slice(offsetSignature, _view.len() - offsetSignature, SynapseTypes.SIGNATURE);
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ PRIVATE FUNCTIONS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @dev No type checks in private functions,
* as the type is checked in the function that called this one.
*/
function _attestationLength(bytes29 _view) private pure returns (uint256) {
return _view.indexUint(OFFSET_ATTESTATION_LENGTH, 2);
}
}
147 changes: 147 additions & 0 deletions packages/contracts/test/libs/Report.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

import { SynapseTest } from "../utils/SynapseTest.sol";
import { Bytes29Test } from "../utils/Bytes29Test.sol";
import { Attestation } from "../../contracts/libs/Attestation.sol";
import { Report } from "../../contracts/libs/Report.sol";
import { SynapseTypes } from "../../contracts/libs/SynapseTypes.sol";
import { TypedMemView } from "../../contracts/libs/TypedMemView.sol";

// solhint-disable func-name-mixedcase

contract ReportTest is SynapseTest, Bytes29Test {
using Attestation for bytes29;
using TypedMemView for bytes29;
using Report for bytes;
using Report for bytes29;

uint256 internal constant NOTARY_PK = 1337;
uint256 internal constant GUARD_PK = 7331;

uint32 internal domain = 1234;
uint32 internal nonce = 4321;
bytes32 internal root = keccak256("root");
Report.Flag internal flag = Report.Flag.Fraud;

bytes internal attestationData;
bytes internal attestation;
bytes internal guardSignature;

function test_formattedCorrectly() public {
bytes29 _view = _createTestView();
_verifyView(_view);
}

function test_formatFraudReport() public {
flag = Report.Flag.Fraud;
_createTestData();
bytes memory report = Report.formatFraudReport(attestation, guardSignature);
_verifyView(report.castToReport());
}

function test_formatValidReport() public {
flag = Report.Flag.Valid;
_createTestData();
bytes memory report = Report.formatValidReport(attestation, guardSignature);
_verifyView(report.castToReport());
}

function test_formatReportData() public {
_createTestData();
bytes memory reportData = Report.formatReportData(flag, attestation);
_verifyReportData(reportData);
}

function test_formatFraudReportData() public {
flag = Report.Flag.Fraud;
_createTestData();
bytes memory reportData = Report.formatFraudReportData(attestation);
_verifyReportData(reportData);
}

function test_formatValidReportData() public {
flag = Report.Flag.Valid;
_createTestData();
bytes memory reportData = Report.formatValidReportData(attestation);
_verifyReportData(reportData);
}

function test_isReport_tooShort() public {
_createTestData();
bytes memory report = abi.encodePacked(
uint16(attestation.length),
uint8(flag),
attestation
);
assertFalse(report.castToReport().isReport());
}

function test_isReport_noAttestation() public {
_createTestData();
bytes memory report = abi.encodePacked(uint16(0), uint8(flag), guardSignature);
assertFalse(report.castToReport().isReport());
}

function test_isReport_invalidAttestation() public {
_createTestData();
// Use attestationData instead of full attestation (i.e. no Notary signature)
bytes memory report = Report.formatReport(flag, attestationData, guardSignature);
bytes29 _view = report.castToReport();
// Sanity check: this is not attestation
assert(!_view.reportedAttestation().isAttestation());
assertFalse(report.castToReport().isReport());
}

function test_isReport_emptyPayload() public {
bytes memory report = bytes("");
assertFalse(report.castToReport().isReport());
}

function test_incorrectType_reportedFraud() public {
_prepareMistypedTest(SynapseTypes.REPORT).reportedFraud();
}

function test_incorrectType_reportedAttestation() public {
_prepareMistypedTest(SynapseTypes.REPORT).reportedAttestation();
}

function test_incorrectType_reportData() public {
_prepareMistypedTest(SynapseTypes.REPORT).reportData();
}

function test_incorrectType_guardSignature() public {
_prepareMistypedTest(SynapseTypes.REPORT).guardSignature();
}

function _createTestData() internal {
attestationData = Attestation.formatAttestationData(domain, nonce, root);
bytes memory notarySig = signMessage(NOTARY_PK, attestationData);
attestation = Attestation.formatAttestation(attestationData, notarySig);
bytes memory reportData = Report.formatReportData(flag, attestation);
guardSignature = signMessage(GUARD_PK, reportData);
}

function _createTestView() internal override returns (bytes29 _view) {
_createTestData();
bytes memory report = Report.formatReport(flag, attestation, guardSignature);
return report.castToReport();
}

function _verifyReportData(bytes memory _reportData) internal {
bytes memory reportData = abi.encodePacked(uint8(flag), attestationData);
assertEq(_reportData, reportData, "!reportData");
}

function _verifyView(bytes29 _view) internal {
assertTrue(_view.isReport());

assertEq(_view.reportedFraud(), flag == Report.Flag.Fraud, "!flag");
assertEq(_view.reportedAttestation().clone(), attestation, "!attestation");

_verifyReportData(_view.reportData().clone());
bytes memory guardSig = signMessage(GUARD_PK, _view.reportData().clone());
assertEq(_view.guardSignature().clone(), guardSig, "!guardSig");
}
}

0 comments on commit dbe6922

Please sign in to comment.