-
Notifications
You must be signed in to change notification settings - Fork 32
/
Destination.sol
244 lines (219 loc) · 13.3 KB
/
Destination.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {Attestation, AttestationLib} from "./libs/memory/Attestation.sol";
import {ByteString} from "./libs/memory/ByteString.sol";
import {AGENT_ROOT_OPTIMISTIC_PERIOD} from "./libs/Constants.sol";
import {IndexOutOfRange, DisputeTimeoutNotOver, NotaryInDispute, OutdatedNonce} from "./libs/Errors.sol";
import {ChainGas, GasData} from "./libs/stack/GasData.sol";
import {AgentStatus, DestinationStatus} from "./libs/Structures.sol";
import {ChainContext} from "./libs/ChainContext.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {AgentSecured} from "./base/AgentSecured.sol";
import {DestinationEvents} from "./events/DestinationEvents.sol";
import {IAgentManager} from "./interfaces/IAgentManager.sol";
import {InterfaceDestination} from "./interfaces/InterfaceDestination.sol";
import {InterfaceLightManager} from "./interfaces/InterfaceLightManager.sol";
import {IStatementInbox} from "./interfaces/IStatementInbox.sol";
import {ExecutionHub} from "./hubs/ExecutionHub.sol";
/// @notice `Destination` contract is used for receiving messages from other chains. It relies on
/// Notary-signed statements to get the truthful states of the remote chains. These states are then
/// used to verify the validity of the messages sent from the remote chains.
/// `Destination` is responsible for the following:
/// - Accepting the Attestations from the local Inbox contract.
/// - Using these Attestations to execute the messages (see parent `ExecutionHub`).
/// - Passing the Agent Merkle Roots from the Attestations to the local LightManager contract,
/// if deployed on a non-Synapse chain.
/// - Keeping track of the remote domains GasData submitted by Notaries, that could be later consumed
/// by the local `GasOracle` contract.
contract Destination is ExecutionHub, DestinationEvents, InterfaceDestination {
using AttestationLib for bytes;
using ByteString for bytes;
// TODO: this could be further optimized in terms of storage
struct StoredAttData {
bytes32 agentRoot;
bytes32 dataHash;
}
struct StoredGasData {
GasData gasData;
uint32 notaryIndex;
uint40 submittedAt;
}
// ══════════════════════════════════════════════════ STORAGE ══════════════════════════════════════════════════════
/// @dev Invariant: this is either current LightManager root,
/// or the pending root to be passed to LightManager once its optimistic period is over.
bytes32 internal _nextAgentRoot;
/// @inheritdoc InterfaceDestination
DestinationStatus public destStatus;
/// @inheritdoc InterfaceDestination
mapping(uint32 => uint32) public lastAttestationNonce;
/// @dev Stored lookup data for all accepted Notary Attestations
StoredAttData[] internal _storedAttestations;
/// @dev Remote domains GasData submitted by Notaries
mapping(uint32 => StoredGasData) internal _storedGasData;
// ═════════════════════════════════════════ CONSTRUCTOR & INITIALIZER ═════════════════════════════════════════════
constructor(uint32 synapseDomain_, address agentManager_, address inbox_)
AgentSecured("0.0.3", synapseDomain_, agentManager_, inbox_)
{} // solhint-disable-line no-empty-blocks
/// @notice Initializes Destination contract:
/// - msg.sender is set as contract owner
function initialize(bytes32 agentRoot) external initializer {
// Initialize Ownable: msg.sender is set as "owner"
__Ownable2Step_init();
// Initialize ReeentrancyGuard
__ReentrancyGuard_init();
// Set Agent Merkle Root in Light Manager
if (localDomain != synapseDomain) {
_nextAgentRoot = agentRoot;
InterfaceLightManager(address(agentManager)).setAgentRoot(agentRoot);
destStatus.agentRootTime = ChainContext.blockTimestamp();
}
// No need to do anything on Synapse Chain, as the agent root is set in BondingManager
}
// ═════════════════════════════════════════════ ACCEPT STATEMENTS ═════════════════════════════════════════════════
/// @inheritdoc InterfaceDestination
function acceptAttestation(
uint32 notaryIndex,
uint256 sigIndex,
bytes memory attPayload,
bytes32 agentRoot,
ChainGas[] memory snapGas
) external onlyInbox returns (bool wasAccepted) {
// Check that we can trust the Notary data: they are not in dispute, and the dispute timeout is over (if any)
if (_notaryDisputeExists(notaryIndex)) revert NotaryInDispute();
if (_notaryDisputeTimeout(notaryIndex)) revert DisputeTimeoutNotOver();
// First, try passing current agent merkle root
// This will revert if payload is not an attestation
Attestation att = attPayload.castToAttestation();
// Check that this Notary hasn't used a more fresh nonce
uint32 attNonce = att.nonce();
if (attNonce <= lastAttestationNonce[notaryIndex]) revert OutdatedNonce();
lastAttestationNonce[notaryIndex] = attNonce;
// This will revert if snapshot root has been previously submitted
_saveAttestation(att, notaryIndex, sigIndex);
_storedAttestations.push(StoredAttData({agentRoot: agentRoot, dataHash: att.dataHash()}));
// Save Agent Root if required, and update the Destination's Status
bool rootPending = passAgentRoot();
destStatus = _saveAgentRoot(rootPending, agentRoot, notaryIndex);
_saveGasData(snapGas, notaryIndex);
return true;
}
// ═══════════════════════════════════════════ AGENT ROOT QUARANTINE ═══════════════════════════════════════════════
/// @inheritdoc InterfaceDestination
function passAgentRoot() public returns (bool rootPending) {
// Agent root is not passed on Synapse Chain, as it could be accessed via BondingManager
if (localDomain == synapseDomain) return false;
bytes32 oldRoot = IAgentManager(agentManager).agentRoot();
bytes32 newRoot = _nextAgentRoot;
// Check if agent root differs from the current one in LightManager
if (oldRoot == newRoot) return false;
DestinationStatus memory status = destStatus;
// Invariant: Notary who supplied `newRoot` was registered as active against `oldRoot`
// So we just need to check the Dispute status of the Notary
if (_notaryDisputeExists(status.notaryIndex)) {
// Remove the pending agent merkle root, as its signer is in dispute
_nextAgentRoot = oldRoot;
return false;
}
// If Notary recently won a Dispute, we can optimistically assume that their passed root is valid.
// However, we need to wait until the Dispute timeout is over, before passing the new root to LightManager.
if (_notaryDisputeTimeout(status.notaryIndex)) {
// We didn't pass anything, but there is a pending root
return true;
}
// Check if agent root optimistic period is over
if (status.agentRootTime + AGENT_ROOT_OPTIMISTIC_PERIOD > block.timestamp) {
// We didn't pass anything, but there is a pending root
return true;
}
// `newRoot` signer was not disputed, and the root optimistic period is over.
// Finally, pass the Agent Merkle Root to LightManager
InterfaceLightManager(address(agentManager)).setAgentRoot(newRoot);
return false;
}
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
/// @inheritdoc InterfaceDestination
// solhint-disable-next-line ordering
function attestationsAmount() external view returns (uint256) {
return _roots.length;
}
/// @inheritdoc InterfaceDestination
function getAttestation(uint256 index) external view returns (bytes memory attPayload, bytes memory attSignature) {
if (index >= _roots.length) revert IndexOutOfRange();
bytes32 snapRoot = _roots[index];
SnapRootData memory rootData = _rootData[snapRoot];
StoredAttData memory storedAtt = _storedAttestations[index];
attPayload = AttestationLib.formatAttestation({
snapRoot_: snapRoot,
dataHash_: storedAtt.dataHash,
nonce_: rootData.attNonce,
blockNumber_: rootData.attBN,
timestamp_: rootData.attTS
});
// Attestation signatures are not required on Synapse Chain, as the attestations could be accessed via Summit.
if (localDomain != synapseDomain) {
attSignature = IStatementInbox(inbox).getStoredSignature(rootData.sigIndex);
}
}
/// @inheritdoc InterfaceDestination
function getGasData(uint32 domain) external view returns (GasData gasData, uint256 dataMaturity) {
StoredGasData memory storedGasData = _storedGasData[domain];
// Form the data to return only if it exists and we can trust it:
// - There is stored gas data for the domain
// - Notary who provided the data is not in dispute
// - Notary who provided the data is not in post-dispute timeout period
// forgefmt: disable-next-item
if (
storedGasData.submittedAt != 0 &&
!_notaryDisputeExists(storedGasData.notaryIndex) &&
!_notaryDisputeTimeout(storedGasData.notaryIndex)
) {
gasData = storedGasData.gasData;
dataMaturity = block.timestamp - storedGasData.submittedAt;
}
// Return empty values if there is no data for the domain, or the notary who provided the data can't be trusted.
}
/// @inheritdoc InterfaceDestination
function nextAgentRoot() external view returns (bytes32) {
// Return current agent root on Synapse Chain for consistency
return localDomain == synapseDomain ? IAgentManager(agentManager).agentRoot() : _nextAgentRoot;
}
// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════
/// @dev Saves Agent Merkle Root from the accepted attestation, if there is
/// no pending root to be passed to LightManager.
/// Returns the updated "last snapshot root / last agent root" status struct.
function _saveAgentRoot(bool rootPending, bytes32 agentRoot, uint32 notaryIndex)
internal
returns (DestinationStatus memory status)
{
status = destStatus;
// Update the timestamp for the latest snapshot root
status.snapRootTime = ChainContext.blockTimestamp();
// No need to save agent roots on Synapse Chain, as they could be accessed via BondingManager
// Don't update agent root, if there is already a pending one
// Update the data for latest agent root only if it differs from the saved one
if (localDomain != synapseDomain && !rootPending && _nextAgentRoot != agentRoot) {
status.agentRootTime = ChainContext.blockTimestamp();
status.notaryIndex = notaryIndex;
_nextAgentRoot = agentRoot;
emit AgentRootAccepted(agentRoot);
}
}
/// @dev Saves updated values from the snapshot's gas data list.
function _saveGasData(ChainGas[] memory snapGas, uint32 notaryIndex) internal {
uint256 statesAmount = snapGas.length;
for (uint256 i = 0; i < statesAmount; i++) {
ChainGas chainGas = snapGas[i];
uint32 domain = chainGas.domain();
// Don't save gas data for the local domain
if (domain == localDomain) continue;
StoredGasData memory storedGasData = _storedGasData[domain];
// Check that the gas data is not already saved
GasData gasData = chainGas.gasData();
if (GasData.unwrap(gasData) == GasData.unwrap(storedGasData.gasData)) continue;
// Save the gas data
_storedGasData[domain] =
StoredGasData({gasData: gasData, notaryIndex: notaryIndex, submittedAt: ChainContext.blockTimestamp()});
}
}
}