-
Notifications
You must be signed in to change notification settings - Fork 32
/
AgentManager.sol
236 lines (202 loc) · 12.4 KB
/
AgentManager.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {FRESH_DATA_TIMEOUT} from "../libs/Constants.sol";
import {
CallerNotInbox,
DisputeAlreadyResolved,
IncorrectAgentDomain,
IndexOutOfRange,
GuardInDispute,
NotaryInDispute,
NotStuck
} from "../libs/Errors.sol";
import {AgentFlag, AgentStatus, DisputeFlag} from "../libs/Structures.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {MessagingBase} from "../base/MessagingBase.sol";
import {AgentManagerEvents} from "../events/AgentManagerEvents.sol";
import {IAgentManager} from "../interfaces/IAgentManager.sol";
import {InterfaceDestination} from "../interfaces/InterfaceDestination.sol";
import {IStatementInbox} from "../interfaces/IStatementInbox.sol";
/// @notice `AgentManager` is used to keep track of all the bonded agents and their statuses.
/// The exact logic of how the agent statuses are stored and updated is implemented in child contracts,
/// and depends on whether the contract is used on Synapse Chain or on other chains.
/// `AgentManager` is responsible for the following:
/// - Keeping track of all the bonded agents and their statuses.
/// - Keeping track of all the disputes between agents.
/// - Notifying `AgentSecured` contracts about the opened and resolved disputes.
/// - Notifying `AgentSecured` contracts about the slashed agents.
abstract contract AgentManager is MessagingBase, AgentManagerEvents, IAgentManager {
struct AgentDispute {
DisputeFlag flag;
uint88 disputePtr;
address fraudProver;
}
// TODO: do we want to store the dispute timestamp?
struct OpenedDispute {
uint32 guardIndex;
uint32 notaryIndex;
uint32 slashedIndex;
}
// ══════════════════════════════════════════════════ STORAGE ══════════════════════════════════════════════════════
address public origin;
address public destination;
address public inbox;
// (agent index => their dispute status)
mapping(uint256 => AgentDispute) internal _agentDispute;
// All disputes ever opened
OpenedDispute[] internal _disputes;
/// @dev gap for upgrade safety
uint256[45] private __GAP; // solhint-disable-line var-name-mixedcase
modifier onlyInbox() {
if (msg.sender != inbox) revert CallerNotInbox();
_;
}
modifier onlyWhenStuck() {
// Check if there has been no fresh data from the Notaries for a while.
(uint40 snapRootTime,,) = InterfaceDestination(destination).destStatus();
if (block.timestamp < FRESH_DATA_TIMEOUT + snapRootTime) revert NotStuck();
_;
}
// ════════════════════════════════════════════════ INITIALIZER ════════════════════════════════════════════════════
// solhint-disable-next-line func-name-mixedcase
function __AgentManager_init(address origin_, address destination_, address inbox_) internal onlyInitializing {
origin = origin_;
destination = destination_;
inbox = inbox_;
}
// ════════════════════════════════════════════════ ONLY INBOX ═════════════════════════════════════════════════════
/// @inheritdoc IAgentManager
// solhint-disable-next-line ordering
function openDispute(uint32 guardIndex, uint32 notaryIndex) external onlyInbox {
// Check that both agents are not in Dispute yet.
if (_agentDispute[guardIndex].flag != DisputeFlag.None) revert GuardInDispute();
if (_agentDispute[notaryIndex].flag != DisputeFlag.None) revert NotaryInDispute();
_disputes.push(OpenedDispute(guardIndex, notaryIndex, 0));
// Dispute is stored at length - 1, but we store the index + 1 to distinguish from "not in dispute".
// TODO: check if we really need to use 88 bits for dispute indexes. Every dispute ends up in one of
// the agents being slashed, so the number of disputes is limited by the number of agents (currently 2**32).
// Thus we can do the unsafe cast to uint88.
uint88 disputePtr = uint88(_disputes.length);
_agentDispute[guardIndex] = AgentDispute(DisputeFlag.Pending, disputePtr, address(0));
_agentDispute[notaryIndex] = AgentDispute(DisputeFlag.Pending, disputePtr, address(0));
// Dispute index is length - 1. Note: report that initiated the dispute has the same index in `Inbox`.
emit DisputeOpened({disputeIndex: disputePtr - 1, guardIndex: guardIndex, notaryIndex: notaryIndex});
_notifyDisputeOpened(guardIndex, notaryIndex);
}
/// @inheritdoc IAgentManager
function slashAgent(uint32 domain, address agent, address prover) external onlyInbox {
_slashAgent(domain, agent, prover);
}
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
/// @inheritdoc IAgentManager
function getAgent(uint256 index) external view returns (address agent, AgentStatus memory status) {
agent = _getAgent(index);
if (agent != address(0)) status = agentStatus(agent);
}
/// @inheritdoc IAgentManager
function agentStatus(address agent) public view returns (AgentStatus memory status) {
status = _storedAgentStatus(agent);
// If agent was proven to commit fraud, but their slashing wasn't completed, return the Fraudulent flag.
if (_agentDispute[_getIndex(agent)].flag == DisputeFlag.Slashed && status.flag != AgentFlag.Slashed) {
status.flag = AgentFlag.Fraudulent;
}
}
/// @inheritdoc IAgentManager
function getDisputesAmount() external view returns (uint256) {
return _disputes.length;
}
/// @inheritdoc IAgentManager
function getDispute(uint256 index)
external
view
returns (
address guard,
address notary,
address slashedAgent,
address fraudProver,
bytes memory reportPayload,
bytes memory reportSignature
)
{
if (index >= _disputes.length) revert IndexOutOfRange();
OpenedDispute memory dispute = _disputes[index];
guard = _getAgent(dispute.guardIndex);
notary = _getAgent(dispute.notaryIndex);
if (dispute.slashedIndex > 0) {
slashedAgent = _getAgent(dispute.slashedIndex);
fraudProver = _agentDispute[dispute.slashedIndex].fraudProver;
}
(reportPayload, reportSignature) = IStatementInbox(inbox).getGuardReport(index);
}
/// @inheritdoc IAgentManager
function disputeStatus(address agent)
external
view
returns (DisputeFlag flag, address rival, address fraudProver, uint256 disputePtr)
{
uint256 agentIndex = _getIndex(agent);
AgentDispute memory agentDispute = _agentDispute[agentIndex];
flag = agentDispute.flag;
fraudProver = agentDispute.fraudProver;
disputePtr = agentDispute.disputePtr;
if (disputePtr > 0) {
OpenedDispute memory dispute = _disputes[disputePtr - 1];
rival = _getAgent(dispute.guardIndex == agentIndex ? dispute.notaryIndex : dispute.guardIndex);
}
}
// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════
/// @dev Hook that is called after agent was slashed in AgentManager and AgentSecured contracts were notified.
// solhint-disable-next-line no-empty-blocks
function _afterAgentSlashed(uint32 domain, address agent, address prover) internal virtual {}
/// @dev Child contract should implement the logic for notifying AgentSecured contracts about the opened dispute.
function _notifyDisputeOpened(uint32 guardIndex, uint32 notaryIndex) internal virtual;
/// @dev Child contract should implement the logic for notifying AgentSecured contracts about the resolved dispute.
function _notifyDisputeResolved(uint32 slashedIndex, uint32 rivalIndex) internal virtual;
/// @dev Slashes the Agent and notifies the local Destination and Origin contracts about the slashed agent.
/// Should be called when the agent fraud was confirmed.
function _slashAgent(uint32 domain, address agent, address prover) internal {
// Check that agent is Active/Unstaking and that the domains match
AgentStatus memory status = _storedAgentStatus(agent);
status.verifyActiveUnstaking();
if (status.domain != domain) revert IncorrectAgentDomain();
// The "stored" agent status is not updated yet, however agentStatus() will return AgentFlag.Fraudulent
emit StatusUpdated(AgentFlag.Fraudulent, domain, agent);
// This will revert if the agent has been slashed earlier
_resolveDispute(status.index, prover);
// Call "after slash" hook - this allows Bonding/Light Manager to add custom "after slash" logic
_afterAgentSlashed(domain, agent, prover);
}
/// @dev Resolves a Dispute between a slashed Agent and their Rival (if there was one).
function _resolveDispute(uint32 slashedIndex, address prover) internal {
AgentDispute memory agentDispute = _agentDispute[slashedIndex];
if (agentDispute.flag == DisputeFlag.Slashed) revert DisputeAlreadyResolved();
agentDispute.flag = DisputeFlag.Slashed;
agentDispute.fraudProver = prover;
_agentDispute[slashedIndex] = agentDispute;
// Check if there was a opened dispute with the slashed agent
uint32 rivalIndex = 0;
if (agentDispute.disputePtr != 0) {
uint256 disputeIndex = agentDispute.disputePtr - 1;
OpenedDispute memory dispute = _disputes[disputeIndex];
_disputes[disputeIndex].slashedIndex = slashedIndex;
// Clear the dispute status for the rival
rivalIndex = dispute.notaryIndex == slashedIndex ? dispute.guardIndex : dispute.notaryIndex;
delete _agentDispute[rivalIndex];
emit DisputeResolved(disputeIndex, slashedIndex, rivalIndex, prover);
}
_notifyDisputeResolved(slashedIndex, rivalIndex);
}
// ══════════════════════════════════════════════ INTERNAL VIEWS ═══════════════════════════════════════════════════
/// @dev Generates leaf to be saved in the Agent Merkle Tree
function _agentLeaf(AgentFlag flag, uint32 domain, address agent) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(flag, domain, agent));
}
/// @dev Returns the last known status for the agent from the Agent Merkle Tree.
/// Note: the actual agent status (returned by `agentStatus()`) may differ, if agent fraud was proven.
function _storedAgentStatus(address agent) internal view virtual returns (AgentStatus memory);
/// @dev Returns agent address for the given index. Returns zero for non existing indexes.
function _getAgent(uint256 index) internal view virtual returns (address);
/// @dev Returns the index of the agent in the Agent Merkle Tree. Returns zero for non existing agents.
function _getIndex(address agent) internal view virtual returns (uint256);
}