-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7df8fe1
commit dbe6922
Showing
2 changed files
with
394 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |