forked from rsksmart/rif-relay
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRelayHub.sol
400 lines (343 loc) · 13.3 KB
/
RelayHub.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
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable not-rely-on-time */
/* solhint-disable avoid-tx-origin */
/* solhint-disable bracket-align */
// SPDX-License-Identifier:MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./utils/Eip712Library.sol";
import "./interfaces/EnvelopingTypes.sol";
import "./interfaces/IRelayHub.sol";
import "./interfaces/IForwarder.sol";
contract RelayHub is IRelayHub {
using SafeMath for uint256;
uint256 public override minimumStake;
uint256 public override minimumUnstakeDelay;
uint256 public override minimumEntryDepositValue;
uint256 public override gasOverhead;
uint256 public override maxWorkerCount;
address public override penalizer;
string public override versionHub = "2.0.1+enveloping.hub.irelayhub";
// maps relay worker's address to its manager's address
mapping(address => bytes32) public override workerToManager;
// maps relay managers to the number of their workers
mapping(address => uint256) public override workerCount;
// maps relay managers to their stakes
mapping(address => StakeInfo) public stakes;
constructor(
address _penalizer,
uint256 _maxWorkerCount,
uint256 _gasOverhead,
uint256 _minimumEntryDepositValue,
uint256 _minimumUnstakeDelay,
uint256 _minimumStake
) public {
require(
_maxWorkerCount > 0 &&
_gasOverhead > 0 &&
_minimumStake > 0 &&
_minimumEntryDepositValue > 0 &&
_minimumUnstakeDelay > 0, "invalid hub init params"
);
penalizer = _penalizer;
maxWorkerCount = _maxWorkerCount;
gasOverhead = _gasOverhead;
minimumUnstakeDelay = _minimumUnstakeDelay;
minimumStake = _minimumStake;
minimumEntryDepositValue = _minimumEntryDepositValue;
}
function registerRelayServer(
string calldata url
) external override {
//relay manager is msg.sender
//Check if Relay Manager is staked
requireManagerStaked(msg.sender);
require(workerCount[msg.sender] > 0, "no relay workers");
emit RelayServerRegistered(msg.sender, url);
}
function disableRelayWorkers(address[] calldata relayWorkers)
external
override
{
//relay manager is msg.sender
uint256 actualWorkerCount = workerCount[msg.sender];
require(
actualWorkerCount >= relayWorkers.length,
"invalid quantity of workers"
);
workerCount[msg.sender] = actualWorkerCount - relayWorkers.length;
//Check if Relay Manager is staked
requireManagerStaked(msg.sender);
bytes32 enabledWorker =
bytes32(uint256(msg.sender) << 4) |
0x0000000000000000000000000000000000000000000000000000000000000001;
bytes32 disabledWorker = bytes32(uint256(msg.sender) << 4);
for (uint256 i = 0; i < relayWorkers.length; i++) {
//The relay manager can only disable its relay workers and only if they are enabled (right-most nibble as 1)
require(
workerToManager[relayWorkers[i]] == enabledWorker,
"Incorrect Manager"
);
//Disabling a worker means putting the right-most nibble to 0
workerToManager[relayWorkers[i]] = disabledWorker;
}
emit RelayWorkersDisabled(
msg.sender,
relayWorkers,
workerCount[msg.sender]
);
}
/**
New relay worker addresses can be added (as enabled workers) as long as they don't have a relay manager aldeady assigned.
*/
function addRelayWorkers(address[] calldata newRelayWorkers)
external
override
{
address relayManager = msg.sender;
workerCount[relayManager] =
workerCount[relayManager] +
newRelayWorkers.length;
require(
workerCount[relayManager] <= maxWorkerCount,
"too many workers"
);
//Check if Relay Manager is staked
requireManagerStaked(relayManager);
bytes32 enabledWorker =
bytes32(uint256(relayManager) << 4) |
0x0000000000000000000000000000000000000000000000000000000000000001;
for (uint256 i = 0; i < newRelayWorkers.length; i++) {
require(
workerToManager[newRelayWorkers[i]] == bytes32(0),
"this worker has a manager"
);
workerToManager[newRelayWorkers[i]] = enabledWorker;
}
emit RelayWorkersAdded(
relayManager,
newRelayWorkers,
workerCount[relayManager]
);
}
function deployCall(
EnvelopingTypes.DeployRequest calldata deployRequest,
bytes calldata signature
) external override {
(signature);
bytes32 managerEntry = workerToManager[msg.sender];
//read last nibble which stores the isWorkerEnabled flag, it must be 1 (true)
require(
managerEntry &
0x0000000000000000000000000000000000000000000000000000000000000001 ==
0x0000000000000000000000000000000000000000000000000000000000000001,
"Not an enabled worker"
);
address manager = address(uint160(uint256(managerEntry >> 4)));
require(
gasleft() >= gasOverhead.add(deployRequest.request.gas),
"Not enough gas left"
);
require(msg.sender == tx.origin, "RelayWorker cannot be a contract");
require(
msg.sender == deployRequest.relayData.relayWorker,
"Not a right worker"
);
requireManagerStaked(manager);
require(
deployRequest.relayData.gasPrice <= tx.gasprice,
"Invalid gas price"
);
bool deploySuccess = Eip712Library.deploy(deployRequest, signature);
if (!deploySuccess) {
assembly {
revert(0, 0)
}
}
}
function relayCall(
EnvelopingTypes.RelayRequest calldata relayRequest,
bytes calldata signature
) external override {
(signature);
require(
gasleft() >= gasOverhead.add(relayRequest.request.gas),
"Not enough gas left"
);
require(msg.sender == tx.origin, "RelayWorker cannot be a contract");
require(
msg.sender == relayRequest.relayData.relayWorker,
"Not a right worker"
);
require(
relayRequest.relayData.gasPrice <= tx.gasprice,
"Invalid gas price"
);
bytes32 managerEntry = workerToManager[msg.sender];
//read last nibble which stores the isWorkerEnabled flag, it must be 1 (true)
require(
managerEntry &
0x0000000000000000000000000000000000000000000000000000000000000001 ==
0x0000000000000000000000000000000000000000000000000000000000000001,
"Not an enabled worker"
);
address manager = address(uint160(uint256(managerEntry >> 4)));
requireManagerStaked(manager);
bool forwarderSuccess;
bool succ;
bytes memory relayedCallReturnValue;
//use succ as relay call success variable
(forwarderSuccess, succ, relayedCallReturnValue) = Eip712Library
.execute(relayRequest, signature);
if (!forwarderSuccess) {
assembly {
revert(
add(relayedCallReturnValue, 32),
mload(relayedCallReturnValue)
)
}
}
if (succ) {
emit TransactionRelayed(
manager,
msg.sender,
keccak256(signature),
relayedCallReturnValue
);
} else {
emit TransactionRelayedButRevertedByRecipient(
manager,
msg.sender,
keccak256(signature),
relayedCallReturnValue
);
}
}
modifier penalizerOnly() {
require(msg.sender == penalizer, "Not penalizer");
_;
}
function penalize(address relayWorker, address payable beneficiary)
external
override
penalizerOnly
{
//Relay worker might be enabled or disabled
address relayManager =
address(uint160(uint256(workerToManager[relayWorker] >> 4)));
require(relayManager != address(0), "Unknown relay worker");
requireManagerStaked(relayManager);
penalizeRelayManager(relayManager, beneficiary);
}
function getStakeInfo(address relayManager)
external
view
override
returns (StakeInfo memory stakeInfo)
{
return stakes[relayManager];
}
/// Slash the stake of the relay relayManager. In order to prevent stake kidnapping, burns half of stake on the way.
/// @param relayManager - entry to penalize
/// @param beneficiary - address that receives half of the penalty amount
function penalizeRelayManager(
address relayManager,
address payable beneficiary
) internal {
StakeInfo storage stakeInfo = stakes[relayManager];
uint256 amount = stakeInfo.stake;
// Half of the stake will be burned (sent to address 0)
stakeInfo.stake = 0;
uint256 toBurn = SafeMath.div(amount, 2);
uint256 reward = SafeMath.sub(amount, toBurn);
// Ether is burned and transferred
address(0).transfer(toBurn);
beneficiary.transfer(reward);
emit StakePenalized(relayManager, beneficiary, reward);
}
// Put a stake for a relayManager and set its unstake delay.
// If the entry does not exist, it is created, and the caller of this function becomes its owner.
// If the entry already exists, only the owner can call this function.
// @param relayManager - address that represents a stake entry and controls relay registrations on relay hubs
// @param unstakeDelay - number of blocks to elapse before the owner can retrieve the stake after calling 'unlock'
function stakeForAddress(address relayManager, uint256 unstakeDelay)
external
payable
override
{
require(
stakes[relayManager].owner == address(0) ||
stakes[relayManager].owner == msg.sender,
"not owner"
);
require(
unstakeDelay >= stakes[relayManager].unstakeDelay,
"unstakeDelay cannot be decreased"
);
require(msg.sender != relayManager, "caller is the relayManager");
require(
stakes[msg.sender].owner == address(0),
"sender is a relayManager itself"
);
//If it is the initial stake, it must meet the entry value
if (stakes[relayManager].owner == address(0)) {
require(
msg.value >= minimumEntryDepositValue,
"Insufficient intitial stake"
);
}
stakes[relayManager].owner = msg.sender;
stakes[relayManager].stake += msg.value;
stakes[relayManager].unstakeDelay = unstakeDelay;
emit StakeAdded(
relayManager,
stakes[relayManager].owner,
stakes[relayManager].stake,
stakes[relayManager].unstakeDelay
);
}
function unlockStake(address relayManager) external override {
StakeInfo storage info = stakes[relayManager];
require(info.owner == msg.sender, "not owner");
require(info.withdrawBlock == 0, "already pending");
info.withdrawBlock = block.number.add(info.unstakeDelay);
emit StakeUnlocked(relayManager, msg.sender, info.withdrawBlock);
}
function withdrawStake(address relayManager) external override {
StakeInfo storage info = stakes[relayManager];
require(info.owner == msg.sender, "not owner");
require(info.withdrawBlock > 0, "Withdrawal is not scheduled");
require(info.withdrawBlock <= block.number, "Withdrawal is not due");
uint256 amount = info.stake;
delete stakes[relayManager];
msg.sender.transfer(amount);
emit StakeWithdrawn(relayManager, msg.sender, amount);
}
modifier ownerOnly(address relayManager) {
StakeInfo storage info = stakes[relayManager];
require(info.owner == msg.sender, "not owner");
_;
}
modifier managerOnly() {
StakeInfo storage info = stakes[msg.sender];
require(info.owner != address(0), "not manager");
_;
}
function requireManagerStaked(address relayManager) internal view {
StakeInfo storage info = stakes[relayManager];
require(
info.stake >= minimumStake && //isAmountSufficient
info.unstakeDelay >= minimumUnstakeDelay && //isDelaySufficient
info.withdrawBlock == 0, //isStakeLocked
"RelayManager not staked"
);
}
function isRelayManagerStaked(address relayManager) external view override returns (bool) {
StakeInfo storage info = stakes[relayManager];
return info.stake >= minimumStake && //isAmountSufficient
info.unstakeDelay >= minimumUnstakeDelay && //isDelaySufficient
info.withdrawBlock == 0; //isStakeLocked
}
}