-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathStatementInbox.sol
538 lines (497 loc) · 27.8 KB
/
StatementInbox.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {Attestation, AttestationLib} from "../libs/memory/Attestation.sol";
import {
AgentNotGuard,
AgentNotNotary,
IncorrectAgentDomain,
IncorrectSnapshotProof,
IncorrectSnapshotRoot,
IncorrectState,
IndexOutOfRange
} from "../libs/Errors.sol";
import {Receipt, ReceiptLib} from "../libs/memory/Receipt.sol";
import {Snapshot, SnapshotLib} from "../libs/memory/Snapshot.sol";
import {State, StateLib} from "../libs/memory/State.sol";
import {AgentStatus} from "../libs/Structures.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {MessagingBase} from "../base/MessagingBase.sol";
import {StatementInboxEvents} from "../events/StatementInboxEvents.sol";
import {IAgentManager} from "../interfaces/IAgentManager.sol";
import {IExecutionHub} from "../interfaces/IExecutionHub.sol";
import {IStateHub} from "../interfaces/IStateHub.sol";
import {IStatementInbox} from "../interfaces/IStatementInbox.sol";
// ═════════════════════════════ EXTERNAL IMPORTS ══════════════════════════════
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/// @notice `StatementInbox` is the entry point for all agent-signed statements. It verifies the
/// agent signatures, and passes the unsigned statements to the contract to consume it via `acceptX` functions. Is is
/// also used to verify the agent-signed statements and initiate the agent slashing, should the statement be invalid.
/// `StatementInbox` is responsible for the following:
/// - Accepting State and Receipt Reports to initiate a dispute between Guard and Notary.
/// - Storing all the Guard Reports with the Guard signature leading to a dispute.
/// - Verifying State/State Reports referencing the local chain and slashing the signer if statement is invalid.
/// - Verifying Receipt/Receipt Reports referencing the local chain and slashing the signer if statement is invalid.
abstract contract StatementInbox is MessagingBase, StatementInboxEvents, IStatementInbox {
using AttestationLib for bytes;
using ReceiptLib for bytes;
using StateLib for bytes;
using SnapshotLib for bytes;
struct StoredReport {
uint256 sigIndex;
bytes statementPayload;
}
// ══════════════════════════════════════════════════ STORAGE ══════════════════════════════════════════════════════
address public agentManager;
address public origin;
address public destination;
// TODO: optimize this
bytes[] internal _storedSignatures;
StoredReport[] internal _storedReports;
/// @dev gap for upgrade safety
uint256[45] private __GAP; // solhint-disable-line var-name-mixedcase
// ════════════════════════════════════════════════ INITIALIZER ════════════════════════════════════════════════════
/// @dev Initializes the contract:
/// - Sets up `msg.sender` as the owner of the contract.
/// - Sets up `agentManager`, `origin`, and `destination`.
// solhint-disable-next-line func-name-mixedcase
function __StatementInbox_init(address agentManager_, address origin_, address destination_)
internal
onlyInitializing
{
agentManager = agentManager_;
origin = origin_;
destination = destination_;
__Ownable2Step_init();
}
// ══════════════════════════════════════════ SUBMIT AGENT STATEMENTS ══════════════════════════════════════════════
/// @inheritdoc IStatementInbox
// solhint-disable-next-line ordering
function submitStateReportWithSnapshot(
uint8 stateIndex,
bytes memory srSignature,
bytes memory snapPayload,
bytes memory snapSignature
) external returns (bool wasAccepted) {
// This will revert if payload is not a snapshot
Snapshot snapshot = snapPayload.castToSnapshot();
// This will revert if the snapshot signer is not a known Notary
(AgentStatus memory notaryStatus,) =
_verifySnapshot({snapshot: snapshot, snapSignature: snapSignature, verifyNotary: true});
// Notary needs to be Active/Unstaking
notaryStatus.verifyActiveUnstaking();
// Check if Notary is active on this chain
_verifyNotaryDomain(notaryStatus.domain);
// This will revert if state index is out of range
State state = snapshot.state(stateIndex);
// This will revert if the report signer is not an known Guard
(AgentStatus memory guardStatus,) = _verifyStateReport(state, srSignature);
// Check that Guard is active
guardStatus.verifyActive();
_saveReport(state.unwrap().clone(), srSignature);
// This will revert if either actor is already in dispute
IAgentManager(agentManager).openDispute(guardStatus.index, notaryStatus.index);
return true;
}
/// @inheritdoc IStatementInbox
function submitStateReportWithAttestation(
uint8 stateIndex,
bytes memory srSignature,
bytes memory snapPayload,
bytes memory attPayload,
bytes memory attSignature
) external returns (bool wasAccepted) {
// This will revert if payload is not a snapshot
Snapshot snapshot = snapPayload.castToSnapshot();
// This will revert if state index is out of range
State state = snapshot.state(stateIndex);
// This will revert if the report signer is not an known Guard
(AgentStatus memory guardStatus,) = _verifyStateReport(state, srSignature);
// Check that Guard is active
guardStatus.verifyActive();
// This will revert if payload is not an attestation
Attestation att = attPayload.castToAttestation();
// This will revert if signer is not an known Notary
(AgentStatus memory notaryStatus,) = _verifyAttestation(att, attSignature);
// Notary needs to be Active/Unstaking
notaryStatus.verifyActiveUnstaking();
// Check if Notary is active on this chain
_verifyNotaryDomain(notaryStatus.domain);
if (snapshot.calculateRoot() != att.snapRoot()) revert IncorrectSnapshotRoot();
_saveReport(state.unwrap().clone(), srSignature);
// This will revert if either actor is already in dispute
IAgentManager(agentManager).openDispute(guardStatus.index, notaryStatus.index);
return true;
}
/// @inheritdoc IStatementInbox
function submitStateReportWithSnapshotProof(
uint8 stateIndex,
bytes memory statePayload,
bytes memory srSignature,
bytes32[] memory snapProof,
bytes memory attPayload,
bytes memory attSignature
) external returns (bool wasAccepted) {
// This will revert if payload is not a state
State state = statePayload.castToState();
// This will revert if the report signer is not an known Guard
(AgentStatus memory guardStatus,) = _verifyStateReport(state, srSignature);
// Check that Guard is active
guardStatus.verifyActive();
// This will revert if payload is not an attestation
Attestation att = attPayload.castToAttestation();
// This will revert if signer is not a known Notary
(AgentStatus memory notaryStatus,) = _verifyAttestation(att, attSignature);
// Notary needs to be Active/Unstaking
notaryStatus.verifyActiveUnstaking();
// Check if Notary is active on this chain
_verifyNotaryDomain(notaryStatus.domain);
// This will revert if any of these is true:
// - Attestation root is not equal to Merkle Root derived from State and Snapshot Proof.
// - Snapshot Proof's first element does not match the State metadata.
// - Snapshot Proof length exceeds Snapshot tree Height.
// - State index is out of range.
_verifySnapshotMerkle(att, stateIndex, state, snapProof);
_saveReport(statePayload, srSignature);
// This will revert if either actor is already in dispute
IAgentManager(agentManager).openDispute(guardStatus.index, notaryStatus.index);
return true;
}
// ══════════════════════════════════════════ VERIFY AGENT STATEMENTS ══════════════════════════════════════════════
/// @inheritdoc IStatementInbox
function verifyReceipt(bytes memory rcptPayload, bytes memory rcptSignature)
external
returns (bool isValidReceipt)
{
// This will revert if payload is not a receipt
Receipt rcpt = rcptPayload.castToReceipt();
// This will revert if the attestation signer is not a known Notary
(AgentStatus memory status, address notary) = _verifyReceipt(rcpt, rcptSignature);
// Notary needs to be Active/Unstaking
status.verifyActiveUnstaking();
isValidReceipt = IExecutionHub(destination).isValidReceipt(rcptPayload);
if (!isValidReceipt) {
emit InvalidReceipt(rcptPayload, rcptSignature);
IAgentManager(agentManager).slashAgent(status.domain, notary, msg.sender);
}
}
/// @inheritdoc IStatementInbox
function verifyReceiptReport(bytes memory rcptPayload, bytes memory rrSignature)
external
returns (bool isValidReport)
{
// This will revert if payload is not a receipt
Receipt rcpt = rcptPayload.castToReceipt();
// This will revert if the report signer is not a known Guard
(AgentStatus memory status, address guard) = _verifyReceiptReport(rcpt, rrSignature);
// Guard needs to be Active/Unstaking
status.verifyActiveUnstaking();
// Report is valid IF AND ONLY IF the reported receipt in invalid
isValidReport = !IExecutionHub(destination).isValidReceipt(rcptPayload);
if (!isValidReport) {
emit InvalidReceiptReport(rcptPayload, rrSignature);
IAgentManager(agentManager).slashAgent(status.domain, guard, msg.sender);
}
}
/// @inheritdoc IStatementInbox
function verifyStateWithAttestation(
uint8 stateIndex,
bytes memory snapPayload,
bytes memory attPayload,
bytes memory attSignature
) external returns (bool isValidState) {
// This will revert if payload is not an attestation
Attestation att = attPayload.castToAttestation();
// This will revert if the attestation signer is not a known Notary
(AgentStatus memory status, address notary) = _verifyAttestation(att, attSignature);
// Notary needs to be Active/Unstaking
status.verifyActiveUnstaking();
// This will revert if payload is not a snapshot
Snapshot snapshot = snapPayload.castToSnapshot();
if (snapshot.calculateRoot() != att.snapRoot()) revert IncorrectSnapshotRoot();
// This will revert if state does not refer to this chain
bytes memory statePayload = snapshot.state(stateIndex).unwrap().clone();
isValidState = IStateHub(origin).isValidState(statePayload);
if (!isValidState) {
emit InvalidStateWithAttestation(stateIndex, statePayload, attPayload, attSignature);
IAgentManager(agentManager).slashAgent(status.domain, notary, msg.sender);
}
}
/// @inheritdoc IStatementInbox
function verifyStateWithSnapshotProof(
uint8 stateIndex,
bytes memory statePayload,
bytes32[] memory snapProof,
bytes memory attPayload,
bytes memory attSignature
) external returns (bool isValidState) {
// This will revert if payload is not an attestation
Attestation att = attPayload.castToAttestation();
// This will revert if the attestation signer is not a known Notary
(AgentStatus memory status, address notary) = _verifyAttestation(att, attSignature);
// Notary needs to be Active/Unstaking
status.verifyActiveUnstaking();
// This will revert if payload is not a state
State state = statePayload.castToState();
// This will revert if any of these is true:
// - Attestation root is not equal to Merkle Root derived from State and Snapshot Proof.
// - Snapshot Proof's first element does not match the State metadata.
// - Snapshot Proof length exceeds Snapshot tree Height.
// - State index is out of range.
_verifySnapshotMerkle(att, stateIndex, state, snapProof);
// This will revert if state does not refer to this chain
isValidState = IStateHub(origin).isValidState(statePayload);
if (!isValidState) {
emit InvalidStateWithAttestation(stateIndex, statePayload, attPayload, attSignature);
IAgentManager(agentManager).slashAgent(status.domain, notary, msg.sender);
}
}
/// @inheritdoc IStatementInbox
function verifyStateWithSnapshot(uint8 stateIndex, bytes memory snapPayload, bytes memory snapSignature)
external
returns (bool isValidState)
{
// This will revert if payload is not a snapshot
Snapshot snapshot = snapPayload.castToSnapshot();
// This will revert if the snapshot signer is not a known Guard/Notary
(AgentStatus memory status, address agent) =
_verifySnapshot({snapshot: snapshot, snapSignature: snapSignature, verifyNotary: false});
// Agent needs to be Active/Unstaking
status.verifyActiveUnstaking();
// This will revert if state does not refer to this chain
isValidState = IStateHub(origin).isValidState(snapshot.state(stateIndex).unwrap().clone());
if (!isValidState) {
emit InvalidStateWithSnapshot(stateIndex, snapPayload, snapSignature);
IAgentManager(agentManager).slashAgent(status.domain, agent, msg.sender);
}
}
/// @inheritdoc IStatementInbox
function verifyStateReport(bytes memory statePayload, bytes memory srSignature)
external
returns (bool isValidReport)
{
// This will revert if payload is not a state
State state = statePayload.castToState();
// This will revert if the report signer is not a known Guard
(AgentStatus memory status, address guard) = _verifyStateReport(state, srSignature);
// Guard needs to be Active/Unstaking
status.verifyActiveUnstaking();
// Report is valid IF AND ONLY IF the reported state in invalid
// This will revert if the reported state does not refer to this chain
isValidReport = !IStateHub(origin).isValidState(statePayload);
if (!isValidReport) {
emit InvalidStateReport(statePayload, srSignature);
IAgentManager(agentManager).slashAgent(status.domain, guard, msg.sender);
}
}
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
/// @inheritdoc IStatementInbox
function getReportsAmount() external view returns (uint256) {
return _storedReports.length;
}
/// @inheritdoc IStatementInbox
function getGuardReport(uint256 index)
external
view
returns (bytes memory statementPayload, bytes memory reportSignature)
{
if (index >= _storedReports.length) revert IndexOutOfRange();
StoredReport memory storedReport = _storedReports[index];
statementPayload = storedReport.statementPayload;
reportSignature = _storedSignatures[storedReport.sigIndex];
}
/// @inheritdoc IStatementInbox
function getStoredSignature(uint256 index) external view returns (bytes memory) {
return _storedSignatures[index];
}
// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════
/// @dev Saves the statement reported by Guard as invalid and the Guard Report signature.
function _saveReport(bytes memory statementPayload, bytes memory reportSignature) internal {
uint256 sigIndex = _saveSignature(reportSignature);
_storedReports.push(StoredReport(sigIndex, statementPayload));
}
/// @dev Saves the signature and returns its index.
function _saveSignature(bytes memory signature) internal returns (uint256 sigIndex) {
sigIndex = _storedSignatures.length;
_storedSignatures.push(signature);
}
// ═══════════════════════════════════════════════ AGENT CHECKS ════════════════════════════════════════════════════
/**
* @dev Recovers a signer from a hashed message, and a EIP-191 signature for it.
* Will revert, if the signer is not a known agent.
* @dev Agent flag could be any of these: Active/Unstaking/Resting/Fraudulent/Slashed
* Further checks need to be performed in a caller function.
* @param hashedStatement Hash of the statement that was signed by an Agent
* @param signature Agent signature for the hashed statement
* @return status Struct representing agent status:
* - flag Unknown/Active/Unstaking/Resting/Fraudulent/Slashed
* - domain Domain where agent is/was active
* - index Index of agent in the Agent Merkle Tree
* @return agent Agent that signed the statement
*/
function _recoverAgent(bytes32 hashedStatement, bytes memory signature)
internal
view
returns (AgentStatus memory status, address agent)
{
bytes32 ethSignedMsg = ECDSA.toEthSignedMessageHash(hashedStatement);
agent = ECDSA.recover(ethSignedMsg, signature);
status = IAgentManager(agentManager).agentStatus(agent);
// Discard signature of unknown agents.
// Further flag checks are supposed to be performed in a caller function.
status.verifyKnown();
}
/// @dev Verifies that Notary signature is active on local domain.
function _verifyNotaryDomain(uint32 notaryDomain) internal view {
// Notary needs to be from the local domain (if contract is not deployed on Synapse Chain).
// Or Notary could be from any domain (if contract is deployed on Synapse Chain).
if (notaryDomain != localDomain && localDomain != synapseDomain) revert IncorrectAgentDomain();
}
// ════════════════════════════════════════ ATTESTATION RELATED CHECKS ═════════════════════════════════════════════
/**
* @dev Internal function to verify the signed attestation payload.
* Reverts if any of these is true:
* - Attestation signer is not a known Notary.
* @param att Typed memory view over attestation payload
* @param attSignature Notary signature for the attestation
* @return status Struct representing agent status, see {_recoverAgent}
* @return notary Notary that signed the snapshot
*/
function _verifyAttestation(Attestation att, bytes memory attSignature)
internal
view
returns (AgentStatus memory status, address notary)
{
// This will revert if signer is not a known agent
(status, notary) = _recoverAgent(att.hashValid(), attSignature);
// Attestation signer needs to be a Notary, not a Guard
if (status.domain == 0) revert AgentNotNotary();
}
/**
* @dev Internal function to verify the signed attestation report payload.
* Reverts if any of these is true:
* - Report signer is not a known Guard.
* @param att Typed memory view over attestation payload that Guard reports as invalid
* @param arSignature Guard signature for the "invalid attestation" report
* @return status Struct representing guard status, see {_recoverAgent}
* @return guard Guard that signed the report
*/
function _verifyAttestationReport(Attestation att, bytes memory arSignature)
internal
view
returns (AgentStatus memory status, address guard)
{
// This will revert if signer is not a known agent
(status, guard) = _recoverAgent(att.hashInvalid(), arSignature);
// Report signer needs to be a Guard, not a Notary
if (status.domain != 0) revert AgentNotGuard();
}
// ══════════════════════════════════════════ RECEIPT RELATED CHECKS ═══════════════════════════════════════════════
/**
* @dev Internal function to verify the signed receipt payload.
* Reverts if any of these is true:
* - Receipt signer is not a known Notary.
* @param rcpt Typed memory view over receipt payload
* @param rcptSignature Notary signature for the receipt
* @return status Struct representing agent status, see {_recoverAgent}
* @return notary Notary that signed the snapshot
*/
function _verifyReceipt(Receipt rcpt, bytes memory rcptSignature)
internal
view
returns (AgentStatus memory status, address notary)
{
// This will revert if signer is not a known agent
(status, notary) = _recoverAgent(rcpt.hashValid(), rcptSignature);
// Receipt signer needs to be a Notary, not a Guard
if (status.domain == 0) revert AgentNotNotary();
}
/**
* @dev Internal function to verify the signed receipt report payload.
* Reverts if any of these is true:
* - Report signer is not a known Guard.
* @param rcpt Typed memory view over receipt payload that Guard reports as invalid
* @param rrSignature Guard signature for the "invalid receipt" report
* @return status Struct representing guard status, see {_recoverAgent}
* @return guard Guard that signed the report
*/
function _verifyReceiptReport(Receipt rcpt, bytes memory rrSignature)
internal
view
returns (AgentStatus memory status, address guard)
{
// This will revert if signer is not a known agent
(status, guard) = _recoverAgent(rcpt.hashInvalid(), rrSignature);
// Report signer needs to be a Guard, not a Notary
if (status.domain != 0) revert AgentNotGuard();
}
// ═══════════════════════════════════════ STATE/SNAPSHOT RELATED CHECKS ═══════════════════════════════════════════
/**
* @dev Internal function to verify the signed snapshot report payload.
* Reverts if any of these is true:
* - Report signer is not a known Guard.
* @param state Typed memory view over state payload that Guard reports as invalid
* @param srSignature Guard signature for the report
* @return status Struct representing guard status, see {_recoverAgent}
* @return guard Guard that signed the report
*/
function _verifyStateReport(State state, bytes memory srSignature)
internal
view
returns (AgentStatus memory status, address guard)
{
// This will revert if signer is not a known agent
(status, guard) = _recoverAgent(state.hashInvalid(), srSignature);
// Report signer needs to be a Guard, not a Notary
if (status.domain != 0) revert AgentNotGuard();
}
/**
* @dev Internal function to verify the signed snapshot payload.
* Reverts if any of these is true:
* - Snapshot signer is not a known Agent.
* - Snapshot signer is not a Notary (if verifyNotary is true).
* @param snapshot Typed memory view over snapshot payload
* @param snapSignature Agent signature for the snapshot
* @param verifyNotary If true, snapshot signer needs to be a Notary, not a Guard
* @return status Struct representing agent status, see {_recoverAgent}
* @return agent Agent that signed the snapshot
*/
function _verifySnapshot(Snapshot snapshot, bytes memory snapSignature, bool verifyNotary)
internal
view
returns (AgentStatus memory status, address agent)
{
// This will revert if signer is not a known agent
(status, agent) = _recoverAgent(snapshot.hashValid(), snapSignature);
// If requested, snapshot signer needs to be a Notary, not a Guard
if (verifyNotary && status.domain == 0) revert AgentNotNotary();
}
// ═══════════════════════════════════════════ MERKLE RELATED CHECKS ═══════════════════════════════════════════════
/**
* @dev Internal function to verify that snapshot roots match.
* Reverts if any of these is true:
* - Attestation root is not equal to Merkle Root derived from State and Snapshot Proof.
* - Snapshot Proof's first element does not match the State metadata.
* - Snapshot Proof length exceeds Snapshot tree Height.
* - State index is out of range.
* @param att Typed memory view over Attestation
* @param stateIndex Index of state in the snapshot
* @param state Typed memory view over the provided state payload
* @param snapProof Raw payload with snapshot data
*/
function _verifySnapshotMerkle(Attestation att, uint8 stateIndex, State state, bytes32[] memory snapProof)
internal
pure
{
// Snapshot proof first element should match State metadata (aka "right sub-leaf")
(, bytes32 rightSubLeaf) = state.subLeafs();
if (snapProof[0] != rightSubLeaf) revert IncorrectSnapshotProof();
// Reconstruct Snapshot Merkle Root using the snapshot proof
// This will revert if:
// - State index is out of range.
// - Snapshot Proof length exceeds Snapshot tree Height.
bytes32 snapshotRoot = SnapshotLib.proofSnapRoot(state.root(), state.origin(), snapProof, stateIndex);
// Snapshot root should match the attestation root
if (att.snapRoot() != snapshotRoot) revert IncorrectSnapshotRoot();
}
}