-
Notifications
You must be signed in to change notification settings - Fork 32
/
BondingManager.sol
349 lines (308 loc) · 19.5 KB
/
BondingManager.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {BONDING_OPTIMISTIC_PERIOD} from "../libs/Constants.sol";
import {
AgentCantBeAdded,
CallerNotDestination,
CallerNotSummit,
DisputeAlreadyResolved,
DisputeNotOpened,
IncorrectAgentDomain,
IncorrectOriginDomain,
IndexOutOfRange,
MustBeSynapseDomain,
SlashAgentOptimisticPeriod,
SynapseDomainForbidden
} from "../libs/Errors.sol";
import {DynamicTree, MerkleMath} from "../libs/merkle/MerkleTree.sol";
import {AgentFlag, AgentStatus, DisputeFlag} from "../libs/Structures.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {AgentManager, IAgentManager} from "./AgentManager.sol";
import {MessagingBase} from "../base/MessagingBase.sol";
import {IAgentSecured} from "../interfaces/IAgentSecured.sol";
import {InterfaceBondingManager} from "../interfaces/InterfaceBondingManager.sol";
import {InterfaceLightManager} from "../interfaces/InterfaceLightManager.sol";
import {InterfaceOrigin} from "../interfaces/InterfaceOrigin.sol";
// ═════════════════════════════ EXTERNAL IMPORTS ══════════════════════════════
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
/// @notice BondingManager keeps track of all existing agents on the Synapse Chain.
/// It utilizes a dynamic Merkle Tree to store the agent information. This enables passing only the
/// latest merkle root of this tree (referenced as the Agent Merkle Root) to the remote chains,
/// so that the agents could "register" themselves by proving their current status against this root.
/// `BondingManager` is responsible for the following:
/// - Keeping track of all existing agents, as well as their statuses. In the MVP version there is no token staking,
/// which will be added in the future. Nonetheless, the agent statuses are still stored in the Merkle Tree, and
/// the agent slashing is still possible, though with no reward/penalty for the reporter/reported.
/// - Marking agents as "ready to be slashed" once their fraud is proven on the local or remote chain. Anyone could
/// complete the slashing by providing the proof of the current agent status against the current Agent Merkle Root.
/// - Sending Manager Message to remote `LightManager` to withdraw collected tips from the remote chain.
/// - Accepting Manager Message from remote `LightManager` to slash agents on the Synapse Chain, when their fraud
/// is proven on the remote chain.
contract BondingManager is AgentManager, InterfaceBondingManager {
using SafeCast for uint256;
// ══════════════════════════════════════════════════ STORAGE ══════════════════════════════════════════════════════
// The address of the Summit contract.
address public summit;
// (agent => their status)
mapping(address => AgentStatus) private _agentMap;
// (domain => past and current agents for domain)
mapping(uint32 => address[]) private _domainAgents;
// A list of all agent accounts. First entry is address(0) to make agent indexes start from 1.
address[] private _agents;
// Merkle Tree for Agents.
// leafs[0] = 0
// leafs[index > 0] = keccak(agentFlag, domain, _agents[index])
DynamicTree private _agentTree;
// ═════════════════════════════════════════ CONSTRUCTOR & INITIALIZER ═════════════════════════════════════════════
constructor(uint32 synapseDomain_) MessagingBase("0.0.3", synapseDomain_) {
if (localDomain != synapseDomain) revert MustBeSynapseDomain();
}
function initialize(address origin_, address destination_, address inbox_, address summit_) external initializer {
__AgentManager_init(origin_, destination_, inbox_);
summit = summit_;
__Ownable2Step_init();
// Insert a zero address to make indexes for Agents start from 1.
// Zeroed index is supposed to be used as a sentinel value meaning "no agent".
_agents.push(address(0));
}
// ════════════════════════════════════════════ AGENTS LOGIC (MVP) ═════════════════════════════════════════════════
// TODO: remove these MVP functions once token staking is implemented
/// @inheritdoc InterfaceBondingManager
function addAgent(uint32 domain, address agent, bytes32[] memory proof) external onlyOwner {
if (domain == synapseDomain) revert SynapseDomainForbidden();
// Check the STORED status of the added agent in the merkle tree
AgentStatus memory status = _storedAgentStatus(agent);
// Agent index in `_agents`
uint32 index;
// Leaf representing currently saved agent information in the tree
bytes32 oldValue;
if (status.flag == AgentFlag.Unknown) {
// Unknown address could be added to any domain
// New agent will need to be added to `_agents` list: could not have more than 2**32 agents
// TODO: consider using more than 32 bits for agent indexes
index = _agents.length.toUint32();
// Current leaf for index is bytes32(0), which is already assigned to `leaf`
_agents.push(agent);
_domainAgents[domain].push(agent);
} else if (status.flag == AgentFlag.Resting && status.domain == domain) {
// Resting agent could be only added back to the same domain
// Agent is already in `_agents`, fetch the saved index
index = status.index;
// Generate the current leaf for the agent
// oldValue includes the domain information, so we didn't had to check it above.
// However, we are still doing this check to have a more appropriate revert string,
// if a resting agent is requesting to be added to another domain.
oldValue = _agentLeaf(AgentFlag.Resting, domain, agent);
} else {
// Any other flag indicates that agent could not be added
revert AgentCantBeAdded();
}
// This will revert if the proof for the old value is incorrect
_updateLeaf(oldValue, proof, AgentStatus(AgentFlag.Active, domain, index), agent);
}
/// @inheritdoc InterfaceBondingManager
function initiateUnstaking(uint32 domain, address agent, bytes32[] memory proof) external onlyOwner {
// Check the CURRENT status of the unstaking agent
AgentStatus memory status = agentStatus(agent);
// Could only initiate the unstaking for the active agent for the domain
status.verifyActive();
if (status.domain != domain) revert IncorrectAgentDomain();
// Leaf representing currently saved agent information in the tree.
// oldValue includes the domain information, so we didn't had to check it above.
// However, we are still doing this check to have a more appropriate revert string,
// if an agent is initiating the unstaking, but specifies incorrect domain.
bytes32 oldValue = _agentLeaf(AgentFlag.Active, domain, agent);
// This will revert if the proof for the old value is incorrect
_updateLeaf(oldValue, proof, AgentStatus(AgentFlag.Unstaking, domain, status.index), agent);
}
/// @inheritdoc InterfaceBondingManager
function completeUnstaking(uint32 domain, address agent, bytes32[] memory proof) external onlyOwner {
// Check the CURRENT status of the unstaking agent
AgentStatus memory status = agentStatus(agent);
// Could only complete the unstaking, if it was previously initiated
// TODO: add more checks (time-based, possibly collecting info from other chains)
status.verifyUnstaking();
if (status.domain != domain) revert IncorrectAgentDomain();
// Leaf representing currently saved agent information in the tree
// oldValue includes the domain information, so we didn't had to check it above.
// However, we are still doing this check to have a more appropriate revert string,
// if an agent is completing the unstaking, but specifies incorrect domain.
bytes32 oldValue = _agentLeaf(AgentFlag.Unstaking, domain, agent);
// This will revert if the proof for the old value is incorrect
_updateLeaf(oldValue, proof, AgentStatus(AgentFlag.Resting, domain, status.index), agent);
}
// ════════════════════════════════════════════════ ONLY OWNER ═════════════════════════════════════════════════════
/// @inheritdoc InterfaceBondingManager
function resolveDisputeWhenStuck(uint32 domain, address slashedAgent) external onlyOwner onlyWhenStuck {
AgentDispute memory slashedDispute = _agentDispute[_getIndex(slashedAgent)];
if (slashedDispute.flag == DisputeFlag.None) revert DisputeNotOpened();
if (slashedDispute.flag == DisputeFlag.Slashed) revert DisputeAlreadyResolved();
// This will revert if domain doesn't match the agent's domain.
_slashAgent({domain: domain, agent: slashedAgent, prover: address(0)});
}
// ══════════════════════════════════════════════ SLASHING LOGIC ═══════════════════════════════════════════════════
/// @inheritdoc InterfaceBondingManager
function completeSlashing(uint32 domain, address agent, bytes32[] memory proof) external {
// Check the CURRENT status of the unstaking agent
AgentStatus memory status = agentStatus(agent);
// Could only complete the slashing, if it was previously initiated
status.verifyFraudulent();
if (status.domain != domain) revert IncorrectAgentDomain();
// Leaf representing currently saved agent information in the tree
// oldValue includes the domain information, so we didn't had to check it above.
// However, we are still doing this check to have a more appropriate revert string,
// if anyone is completing the slashing, but specifies incorrect domain.
bytes32 oldValue = _getLeaf(agent);
// This will revert if the proof for the old value is incorrect
_updateLeaf(oldValue, proof, AgentStatus(AgentFlag.Slashed, domain, status.index), agent);
}
/// @inheritdoc InterfaceBondingManager
function remoteSlashAgent(uint32 msgOrigin, uint256 proofMaturity, uint32 domain, address agent, address prover)
external
returns (bytes4 magicValue)
{
// Only destination can pass Manager Messages
if (msg.sender != destination) revert CallerNotDestination();
// Check that merkle proof is mature enough
// TODO: separate constant for slashing optimistic period
if (proofMaturity < BONDING_OPTIMISTIC_PERIOD) revert SlashAgentOptimisticPeriod();
// TODO: do we need to save domain where the agent was slashed?
// Message needs to be sent from the remote chain
if (msgOrigin == localDomain) revert IncorrectOriginDomain();
// Slash agent and notify local AgentSecured contracts
_slashAgent(domain, agent, prover);
// Magic value to return is selector of the called function
return this.remoteSlashAgent.selector;
}
// ════════════════════════════════════════════════ TIPS LOGIC ═════════════════════════════════════════════════════
/// @inheritdoc InterfaceBondingManager
function withdrawTips(address recipient, uint32 origin_, uint256 amount) external {
// Only Summit can withdraw tips
if (msg.sender != summit) revert CallerNotSummit();
if (origin_ == localDomain) {
// Call local Origin to withdraw tips
InterfaceOrigin(address(origin)).withdrawTips(recipient, amount);
} else {
// For remote chains: send a manager message to remote LightManager to handle the withdrawal
// remoteWithdrawTips(msgOrigin, proofMaturity, recipient, amount) with the first two security args omitted
InterfaceOrigin(origin).sendManagerMessage({
destination: origin_,
optimisticPeriod: BONDING_OPTIMISTIC_PERIOD,
payload: abi.encodeWithSelector(InterfaceLightManager.remoteWithdrawTips.selector, recipient, amount)
});
}
}
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
/// @inheritdoc IAgentManager
function agentRoot() external view override returns (bytes32) {
return _agentTree.root;
}
/// @inheritdoc InterfaceBondingManager
function getActiveAgents(uint32 domain) external view returns (address[] memory agents) {
uint256 amount = _domainAgents[domain].length;
agents = new address[](amount);
uint256 activeAgents = 0;
for (uint256 i = 0; i < amount; ++i) {
address agent = _domainAgents[domain][i];
if (agentStatus(agent).flag == AgentFlag.Active) {
agents[activeAgents++] = agent;
}
}
if (activeAgents != amount) {
// Shrink the returned array by storing the required length in memory
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(agents, activeAgents)
}
}
}
/// @inheritdoc InterfaceBondingManager
function agentLeaf(address agent) external view returns (bytes32 leaf) {
return _getLeaf(agent);
}
/// @inheritdoc InterfaceBondingManager
function leafsAmount() external view returns (uint256 amount) {
return _agents.length;
}
/// @inheritdoc InterfaceBondingManager
function getProof(address agent) external view returns (bytes32[] memory proof) {
bytes32[] memory leafs = allLeafs();
// Use the STORED agent status from the merkle tree
AgentStatus memory status = _storedAgentStatus(agent);
// Use next available index for unknown agents
uint256 index = status.flag == AgentFlag.Unknown ? _agents.length : status.index;
return MerkleMath.calculateProof(leafs, index);
}
/// @inheritdoc InterfaceBondingManager
function allLeafs() public view returns (bytes32[] memory leafs) {
return getLeafs(0, _agents.length);
}
/// @inheritdoc InterfaceBondingManager
function getLeafs(uint256 indexFrom, uint256 amount) public view returns (bytes32[] memory leafs) {
uint256 amountTotal = _agents.length;
if (indexFrom >= amountTotal) revert IndexOutOfRange();
if (indexFrom + amount > amountTotal) {
amount = amountTotal - indexFrom;
}
leafs = new bytes32[](amount);
for (uint256 i = 0; i < amount; ++i) {
leafs[i] = _getLeaf(indexFrom + i);
}
}
// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════
/// @dev Updates value in the Agent Merkle Tree to reflect the `newStatus`.
/// Will revert, if supplied proof for the old value is incorrect.
function _updateLeaf(bytes32 oldValue, bytes32[] memory proof, AgentStatus memory newStatus, address agent)
internal
{
// New leaf value for the agent in the Agent Merkle Tree
bytes32 newValue = _agentLeaf(newStatus.flag, newStatus.domain, agent);
// This will revert if the proof for the old value is incorrect
bytes32 newRoot = _agentTree.update(newStatus.index, oldValue, proof, newValue);
_agentMap[agent] = newStatus;
emit StatusUpdated(newStatus.flag, newStatus.domain, agent);
emit RootUpdated(newRoot);
}
/// @dev Notify local AgentSecured contracts about the opened dispute.
function _notifyDisputeOpened(uint32 guardIndex, uint32 notaryIndex) internal override {
IAgentSecured(destination).openDispute(guardIndex, notaryIndex);
IAgentSecured(summit).openDispute(guardIndex, notaryIndex);
}
/// @dev Notify local AgentSecured contracts about the resolved dispute.
function _notifyDisputeResolved(uint32 slashedIndex, uint32 rivalIndex) internal override {
IAgentSecured(destination).resolveDispute(slashedIndex, rivalIndex);
IAgentSecured(summit).resolveDispute(slashedIndex, rivalIndex);
}
// ══════════════════════════════════════════════ INTERNAL VIEWS ═══════════════════════════════════════════════════
/// @dev Returns the status of the agent.
function _storedAgentStatus(address agent) internal view override returns (AgentStatus memory) {
return _agentMap[agent];
}
/// @dev Returns agent address for the given index. Returns zero for non existing indexes.
function _getAgent(uint256 index) internal view override returns (address agent) {
if (index < _agents.length) {
agent = _agents[index];
}
}
/// @dev Returns the index of the agent in the Agent Merkle Tree. Returns zero for non existing agents.
function _getIndex(address agent) internal view override returns (uint256 index) {
return _agentMap[agent].index;
}
/// @dev Returns the current leaf representing agent in the Agent Merkle Tree.
function _getLeaf(address agent) internal view returns (bytes32 leaf) {
// Get the agent status STORED in the merkle tree
AgentStatus memory status = _storedAgentStatus(agent);
if (status.flag != AgentFlag.Unknown) {
return _agentLeaf(status.flag, status.domain, agent);
}
// Return empty leaf for unknown _agents
}
/// @dev Returns a leaf from the Agent Merkle Tree with a given index.
function _getLeaf(uint256 index) internal view returns (bytes32 leaf) {
if (index != 0) {
return _getLeaf(_agents[index]);
}
// Return empty leaf for a zero index
}
}