-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathServiceManager.sol
More file actions
687 lines (589 loc) · 29.7 KB
/
Copy pathServiceManager.sol
File metadata and controls
687 lines (589 loc) · 29.7 KB
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
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {GenericManager} from "./GenericManager.sol";
import {OperatorSignedHashes} from "./utils/OperatorSignedHashes.sol";
import "./interfaces/IService.sol";
import "./interfaces/IServiceTokenUtility.sol";
// Operator whitelist interface
interface IOperatorWhitelist {
/// @dev Gets operator whitelisting status.
/// @param serviceId Service Id.
/// @param operator Operator address.
/// @return status Whitelisting status.
function isOperatorWhitelisted(uint256 serviceId, address operator) external view returns (bool status);
}
interface IIdentityRegistryBridger {
/// @dev Registers 8004 agent Id corresponding to service Id.
/// @param serviceId Service Id.
/// @return agentId Corresponding 8004 agent Id.
function register(uint256 serviceId) external returns (uint256 agentId);
/// @dev Updates 8004 agent Id wallet corresponding to service Id multisig.
/// @param serviceId Service Id.
/// @param oldMultisig Old multisig address.
/// @param newMultisig New multisig address.
function updateAgentWallet(uint256 serviceId, address oldMultisig, address newMultisig) external;
}
interface IServiceRegistry {
enum ServiceState {
NonExistent,
PreRegistration,
ActiveRegistration,
FinishedRegistration,
Deployed,
TerminatedBonded
}
/// @dev Gets service instance params.
/// @param serviceId Service Id.
/// @return securityDeposit Registration activation deposit.
/// @return multisig Service multisig address.
/// @return configHash IPFS hashes pointing to the config metadata.
/// @return threshold Agent instance signers threshold.
/// @return maxNumAgentInstances Total number of agent instances.
/// @return numAgentInstances Actual number of agent instances.
/// @return state Service state.
function mapServices(uint256 serviceId)
external
view
returns (
uint96 securityDeposit,
address multisig,
bytes32 configHash,
uint32 threshold,
uint32 maxNumAgentInstances,
uint32 numAgentInstances,
ServiceState state
);
/// @dev Gets service total supply.
function totalSupply() external view returns (uint256);
}
// ERC721 interface
interface IERC721 {
/// @dev Gets the owner of the token Id.
/// @param tokenId Token Id.
/// @return Token Id owner address.
function ownerOf(uint256 tokenId) external view returns (address);
}
/// @dev Storage is already initialized.
error AlreadyInitialized();
/// @dev Multisig is already bound to a different service.
/// @param multisig Multisig address.
/// @param existingServiceId Service Id the multisig is already bound to.
/// @param serviceId Service Id attempting to bind the multisig.
error MultisigAlreadyBound(address multisig, uint256 existingServiceId, uint256 serviceId);
/// @title Service Manager - Periphery smart contract for managing services with custom ERC20 tokens or ETH
/// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
/// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
/// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz>
contract ServiceManager is GenericManager, OperatorSignedHashes {
event OperatorWhitelistUpdated(address indexed operatorWhitelist);
event IdentityRegistryBridgerUpdated(address indexed identityRegistryBridger);
event ImplementationUpdated(address indexed implementation);
event CreateMultisig(address indexed multisig);
event MultisigBound(address indexed multisig, uint256 indexed serviceId);
event UnbondWithSignatureExecuted(address indexed operator, uint256 indexed serviceId, uint256 refund);
event RegisterAgentsWithSignatureExecuted(
address indexed operator, uint256 indexed serviceId, address[] agentInstances, uint32[] agentIds
);
// Name of a signing domain
string public constant NAME = "OLAS Service Manager";
// Version number
string public constant VERSION = "1.2.0";
// Service Manager proxy address slot
// keccak256("PROXY_SERVICE_MANAGER") = "0xe39e69948a448ce9239ad71b908b6c5b46225f86ffa735b25a8cd64080315855"
bytes32 public constant PROXY_SERVICE_MANAGER = 0xe39e69948a448ce9239ad71b908b6c5b46225f86ffa735b25a8cd64080315855;
// A well-known representation of ETH as address
address public constant ETH_TOKEN_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
// Bond wrapping constant
uint96 public constant BOND_WRAPPER = 1;
// Service Registry address
address public immutable serviceRegistry;
// Service Registry Token Utility address
address public immutable serviceRegistryTokenUtility;
// Operator whitelist address
address public operatorWhitelist;
// 8004 Identity Registry Bridger address
address public identityRegistryBridger;
// Reentrancy lock
uint256 internal _locked = 1;
// Global multisig -> owning service Id binding. A multisig belongs to at most one service.
mapping(address => uint256) public mapMultisigServiceIds;
/// @dev ServiceManager constructor.
/// @param _serviceRegistry Service Registry address.
/// @param _serviceRegistryTokenUtility Service Registry Token Utility address.
constructor(address _serviceRegistry, address _serviceRegistryTokenUtility) OperatorSignedHashes(NAME, VERSION) {
// Check for the Service Registry related contract zero addresses
if (_serviceRegistry == address(0) || _serviceRegistryTokenUtility == address(0)) {
revert ZeroAddress();
}
serviceRegistry = _serviceRegistry;
serviceRegistryTokenUtility = _serviceRegistryTokenUtility;
}
/// @dev Initializes proxy contract storage.
function initialize() external {
// Check if contract is already initialized
if (owner != address(0)) {
revert AlreadyInitialized();
}
owner = msg.sender;
}
/// @dev Sets the operator whitelist contract address.
/// @param newOperatorWhitelist New operator whitelist contract address.
function setOperatorWhitelist(address newOperatorWhitelist) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
operatorWhitelist = newOperatorWhitelist;
emit OperatorWhitelistUpdated(newOperatorWhitelist);
}
/// @dev Sets identity registry bridger contract address.
/// @param newIdentityRegistryBridger New identity registry bridger contract address.
function setIdentityRegistryBridger(address newIdentityRegistryBridger) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
identityRegistryBridger = newIdentityRegistryBridger;
emit IdentityRegistryBridgerUpdated(newIdentityRegistryBridger);
}
/// @dev Changes implementation contract address.
/// @notice Make sure implementation contract has function to change its implementation.
/// @param implementation Implementation contract address.
function changeImplementation(address implementation) external {
// Check for contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for zero address
if (implementation == address(0)) {
revert ZeroAddress();
}
// Store implementation address under designated storage slot
assembly {
sstore(PROXY_SERVICE_MANAGER, implementation)
}
emit ImplementationUpdated(implementation);
}
/// @dev Creates a new service.
/// @param serviceOwner Individual that creates and controls a service.
/// @param token ERC20 token address for the security deposit, or ETH.
/// @param configHash IPFS hash pointing to the config metadata.
/// @param agentIds Canonical agent Ids.
/// @param agentParams Number of agent instances and required bond to register an instance in the service.
/// @param threshold Threshold for a multisig composed by agents.
/// @return serviceId Created service Id.
function create(
address serviceOwner,
address token,
bytes32 configHash,
uint32[] memory agentIds,
IService.AgentParams[] memory agentParams,
uint32 threshold
) external returns (uint256 serviceId) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check if the minting is paused
if (paused) {
revert Paused();
}
// Check for the zero address
if (token == address(0)) {
revert ZeroAddress();
}
// Check for the custom ERC20 token or ETH based bond
if (token == ETH_TOKEN_ADDRESS) {
// Call the original ServiceRegistry contract function
serviceId = IService(serviceRegistry).create(serviceOwner, configHash, agentIds, agentParams, threshold);
} else {
// Wrap agent params with just 1 WEI bond going to the original ServiceRegistry contract,
// and actual token bonds being recorded with the ServiceRegistryTokenUtility contract
uint256 numAgents = agentParams.length;
uint256[] memory bonds = new uint256[](numAgents);
for (uint256 i = 0; i < numAgents; ++i) {
// Check for the zero bond value
if (agentParams[i].bond == 0) {
revert ZeroValue();
}
// Copy actual bond values for each agent Id
bonds[i] = agentParams[i].bond;
// Wrap bonds with the BOND_WRAPPER value for the original ServiceRegistry contract
agentParams[i].bond = BOND_WRAPPER;
}
// Get created service Id
serviceId = IServiceRegistry(serviceRegistry).totalSupply() + 1;
// Create a token-related record for the service
IServiceTokenUtility(serviceRegistryTokenUtility).createWithToken(serviceId, token, agentIds, bonds);
// Call the original ServiceRegistry contract function
IService(serviceRegistry).create(serviceOwner, configHash, agentIds, agentParams, threshold);
}
// 8004 Identity Registry workflow
if (identityRegistryBridger != address(0)) {
// Register corresponding 8004 agent Id
IIdentityRegistryBridger(identityRegistryBridger).register(serviceId);
}
_locked = 1;
}
/// @dev Updates a service in a CRUD way.
/// @param token ERC20 token address for the security deposit, or ETH.
/// @param configHash IPFS hash pointing to the config metadata.
/// @param agentIds Canonical agent Ids.
/// @param agentParams Number of agent instances and required bond to register an instance in the service.
/// @param threshold Threshold for a multisig composed by agents.
/// @param serviceId Service Id to be updated.
/// @return success True, if function executed successfully.
function update(
address token,
bytes32 configHash,
uint32[] memory agentIds,
IService.AgentParams[] memory agentParams,
uint32 threshold,
uint256 serviceId
) external returns (bool success) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the zero address
if (token == address(0)) {
revert ZeroAddress();
}
uint256 numAgents = agentParams.length;
if (token == ETH_TOKEN_ADDRESS) {
// If any of the slots is a non-zero, the correspondent bond cannot be zero
for (uint256 i = 0; i < numAgents; ++i) {
// Check for the zero bond value
if (agentParams[i].slots > 0 && agentParams[i].bond == 0) {
revert ZeroValue();
}
}
// Call the original ServiceRegistry contract function
success =
IService(serviceRegistry).update(msg.sender, configHash, agentIds, agentParams, threshold, serviceId);
// Reset the service token-based data
// This function still needs to be called as the previous token could be a custom ERC20 token
IServiceTokenUtility(serviceRegistryTokenUtility).resetServiceToken(serviceId);
} else {
// Wrap agent params with just 1 WEI bond going to the original ServiceRegistry contract,
// and actual token bonds being recorded with the ServiceRegistryTokenUtility contract
uint256[] memory bonds = new uint256[](numAgents);
for (uint256 i = 0; i < numAgents; ++i) {
// Copy actual bond values for each agent Id that has at least one slot in the updated service
if (agentParams[i].slots > 0) {
// Check for the zero bond value
if (agentParams[i].bond == 0) {
revert ZeroValue();
}
bonds[i] = agentParams[i].bond;
// Wrap bonds with the BOND_WRAPPER value for the original ServiceRegistry contract
agentParams[i].bond = BOND_WRAPPER;
}
}
// Update relevant data in the ServiceRegistryTokenUtility contract
// We follow the optimistic design where existing bonds are just overwritten without a clearing
// bond values of agent Ids that are not going to be used in the service. This is coming from the fact
// that all the checks are done on the original ServiceRegistry side
IServiceTokenUtility(serviceRegistryTokenUtility).createWithToken(serviceId, token, agentIds, bonds);
// Call the original ServiceRegistry contract function
success =
IService(serviceRegistry).update(msg.sender, configHash, agentIds, agentParams, threshold, serviceId);
}
_locked = 1;
}
/// @dev Activates the service and its sensitive components.
/// @param serviceId Correspondent service Id.
/// @return success True, if function executed successfully.
function activateRegistration(uint256 serviceId) external payable returns (bool success) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Record the actual ERC20 security deposit
bool isTokenSecured =
IServiceTokenUtility(serviceRegistryTokenUtility).activateRegistrationTokenDeposit(serviceId);
// Activate registration in the original ServiceRegistry contract
if (isTokenSecured) {
// Check for msg.value
if (msg.value != BOND_WRAPPER) {
revert IncorrectRegistrationDepositValue(msg.value, BOND_WRAPPER, serviceId);
}
// If the service Id is based on the ERC20 token, the provided value to the standard registration is 1
success = IService(serviceRegistry).activateRegistration{value: BOND_WRAPPER}(msg.sender, serviceId);
} else {
// Otherwise follow the standard msg.value path
success = IService(serviceRegistry).activateRegistration{value: msg.value}(msg.sender, serviceId);
}
_locked = 1;
}
/// @dev Registers agent instances.
/// @param serviceId Service Id to be updated.
/// @param agentInstances Agent instance addresses.
/// @param agentIds Canonical Ids of the agent correspondent to the agent instance.
/// @return success True, if function executed successfully.
function registerAgents(uint256 serviceId, address[] memory agentInstances, uint32[] memory agentIds)
external
payable
returns (bool success)
{
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
if (operatorWhitelist != address(0)) {
// Check if the operator is whitelisted
if (!IOperatorWhitelist(operatorWhitelist).isOperatorWhitelisted(serviceId, msg.sender)) {
revert WrongOperator(serviceId);
}
}
// Record the actual ERC20 bond
bool isTokenSecured = IServiceTokenUtility(serviceRegistryTokenUtility)
.registerAgentsTokenDeposit(msg.sender, serviceId, agentIds);
// Register agent instances in a main ServiceRegistry contract
if (isTokenSecured) {
// Get total bond wrapper value
uint256 totalBond = agentInstances.length * BOND_WRAPPER;
// Check for msg.value
if (msg.value != totalBond) {
revert IncorrectAgentBondingValue(msg.value, totalBond, serviceId);
}
// If the service Id is based on the ERC20 token, the provided value to the standard registration is 1
// multiplied by the number of agent instances
success = IService(serviceRegistry).registerAgents{value: totalBond}(
msg.sender, serviceId, agentInstances, agentIds
);
} else {
// Otherwise follow the standard msg.value path
success = IService(serviceRegistry).registerAgents{value: msg.value}(
msg.sender, serviceId, agentInstances, agentIds
);
}
_locked = 1;
}
/// @dev Creates multisig instance controlled by the set of service agent instances and deploys the service.
/// @param serviceId Correspondent service Id.
/// @param multisigImplementation Multisig implementation address.
/// @param data Data payload for the multisig creation.
/// @return multisig Address of the created multisig.
function deploy(uint256 serviceId, address multisigImplementation, bytes memory data)
external
returns (address multisig)
{
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Get current service multisig
(, address lastMultisig,,,,,) = IServiceRegistry(serviceRegistry).mapServices(serviceId);
// Create or update multisig instance
multisig = IService(serviceRegistry).deploy(msg.sender, serviceId, multisigImplementation, data);
// Check for zero address
if (multisig == address(0)) {
revert ZeroAddress();
}
// Bind multisig to service: revert if owned by another, claim if unowned, release the previous one on change
uint256 boundServiceId = mapMultisigServiceIds[multisig];
if (boundServiceId != serviceId) {
if (boundServiceId != 0) {
revert MultisigAlreadyBound(multisig, boundServiceId, serviceId);
}
mapMultisigServiceIds[multisig] = serviceId;
emit MultisigBound(multisig, serviceId);
// Release the service's previous multisig only if it is still bound to this service
if (lastMultisig != address(0) && lastMultisig != multisig && mapMultisigServiceIds[lastMultisig] == serviceId) {
mapMultisigServiceIds[lastMultisig] = 0;
emit MultisigBound(lastMultisig, 0);
}
}
// 8004 Identity Registry workflow: check if current and last multisigs are different
if ((identityRegistryBridger != address(0)) && (multisig != lastMultisig)) {
// Update corresponding multisig records and unset wallet in 8004 agent Id, if required
IIdentityRegistryBridger(identityRegistryBridger).updateAgentWallet(serviceId, lastMultisig, multisig);
}
emit CreateMultisig(multisig);
_locked = 1;
}
/// @dev Back-fills the multisig <-> service binding for services deployed before this upgrade.
/// @notice Permissionless and idempotent: for each service Id it records only that service's OWN current
/// multisig (read from the registry), and skips entries with no multisig or already bound. It can
/// therefore only ever write the registry's true binding and cannot forge a foreign one. Anyone may
/// call it to back-fill the existing fleet in bulk.
/// @param serviceIds Set of service Ids to back-fill.
function bindMultisig(uint256[] calldata serviceIds) external {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
for (uint256 i = 0; i < serviceIds.length; ++i) {
(, address multisig,,,,,) = IServiceRegistry(serviceRegistry).mapServices(serviceIds[i]);
// Check for multisig to be non zero and not yet recorded
if (multisig != address(0) && mapMultisigServiceIds[multisig] == 0) {
mapMultisigServiceIds[multisig] = serviceIds[i];
emit MultisigBound(multisig, serviceIds[i]);
}
}
_locked = 1;
}
/// @dev Terminates the service.
/// @param serviceId Service Id.
/// @return success True, if function executed successfully.
/// @return refund Refund for the service owner.
function terminate(uint256 serviceId) external returns (bool success, uint256 refund) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Withdraw the ERC20 token if the service is token-based
uint256 tokenRefund = IServiceTokenUtility(serviceRegistryTokenUtility).terminateTokenRefund(serviceId);
// Terminate the service with the regular service registry routine
(success, refund) = IService(serviceRegistry).terminate(msg.sender, serviceId);
// If the service is token-based, the actual refund is provided via the serviceRegistryTokenUtility contract
if (tokenRefund > 0) {
refund = tokenRefund;
}
_locked = 1;
}
/// @dev Unbonds agent instances of the operator from the service.
/// @param serviceId Service Id.
/// @return success True, if function executed successfully.
/// @return refund The amount of refund returned to the operator.
function unbond(uint256 serviceId) external returns (bool success, uint256 refund) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Withdraw the ERC20 token if the service is token-based
uint256 tokenRefund = IServiceTokenUtility(serviceRegistryTokenUtility).unbondTokenRefund(msg.sender, serviceId);
// Unbond with the regular service registry routine
(success, refund) = IService(serviceRegistry).unbond(msg.sender, serviceId);
// If the service is token-based, the actual refund is provided via the serviceRegistryTokenUtility contract
if (tokenRefund > 0) {
refund = tokenRefund;
}
_locked = 1;
}
/// @dev Unbonds agent instances of the operator by the service owner via the operator's pre-signed message hash.
/// @notice Note that this function accounts for the operator being the EOA, or the contract that has an
/// isValidSignature() function that would confirm the message hash was signed by the operator contract.
/// Otherwise, if the message hash has been pre-approved, the corresponding map of hashes is going to
/// to verify the signed hash, similar to the Safe contract implementation in v1.3.0:
/// https://github.com/safe-global/safe-contracts/blob/186a21a74b327f17fc41217a927dea7064f74604/contracts/GnosisSafe.sol#L240-L304
/// Also note that only the service owner is able to call this function on behalf of the operator.
/// @param operator Operator address that signed the unbond message hash.
/// @param serviceId Service Id.
/// @param signature Signature byte array associated with operator message hash signature.
/// @return success True, if the function executed successfully.
/// @return refund The amount of refund returned to the operator.
function unbondWithSignature(address operator, uint256 serviceId, bytes memory signature)
external
returns (bool success, uint256 refund)
{
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check the service owner
address serviceOwner = IERC721(serviceRegistry).ownerOf(serviceId);
if (msg.sender != serviceOwner) {
revert OwnerOnly(msg.sender, serviceOwner);
}
// Get the (operator | serviceId) nonce for the unbond message
// Push a pair of key defining variables into one key. Service Id or operator are not enough by themselves
// as another service might use the operator address at the same time frame
// operator occupies first 160 bits
uint256 operatorService = uint256(uint160(operator));
// serviceId occupies next 32 bits
operatorService |= serviceId << 160;
uint256 nonce = mapOperatorUnbondNonces[operatorService];
// Get the unbond message hash
bytes32 msgHash = getUnbondHash(operator, serviceOwner, serviceId, nonce);
// Verify the signed hash against the operator address
_verifySignedHash(operator, msgHash, signature);
// Update corresponding nonce value
nonce++;
mapOperatorUnbondNonces[operatorService] = nonce;
// Withdraw the ERC20 token if the service is token-based
uint256 tokenRefund = IServiceTokenUtility(serviceRegistryTokenUtility).unbondTokenRefund(operator, serviceId);
// Unbond with the regular service registry routine
(success, refund) = IService(serviceRegistry).unbond(operator, serviceId);
// If the service is token-based, the actual refund is provided via the serviceRegistryTokenUtility contract
if (tokenRefund > 0) {
refund = tokenRefund;
}
emit UnbondWithSignatureExecuted(operator, serviceId, refund);
_locked = 1;
}
/// @dev Registers agent instances of the operator by the service owner via the operator's pre-signed message hash.
/// @notice Note that this function accounts for the operator being the EOA, or the contract that has an
/// isValidSignature() function that would confirm the message hash was signed by the operator contract.
/// Otherwise, if the message hash has been pre-approved, the corresponding map of hashes is going to
/// to verify the signed hash, similar to the Safe contract implementation in v1.3.0:
/// https://github.com/safe-global/safe-contracts/blob/186a21a74b327f17fc41217a927dea7064f74604/contracts/GnosisSafe.sol#L240-L304
/// Also note that only the service owner is able to call this function on behalf of the operator.
/// @param operator Operator address that signed the register agents message hash.
/// @param serviceId Service Id.
/// @param agentInstances Agent instance addresses.
/// @param agentIds Canonical Ids of the agent correspondent to the agent instance.
/// @param signature Signature byte array associated with operator message hash signature.
/// @return success True, if the the function executed successfully.
function registerAgentsWithSignature(
address operator,
uint256 serviceId,
address[] memory agentInstances,
uint32[] memory agentIds,
bytes memory signature
) external payable returns (bool success) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check the service owner
address serviceOwner = IERC721(serviceRegistry).ownerOf(serviceId);
if (msg.sender != serviceOwner) {
revert OwnerOnly(msg.sender, serviceOwner);
}
// Get the (operator | serviceId) nonce for the registerAgents message
// Push a pair of key defining variables into one key. Service Id or operator are not enough by themselves
// as another service might use the operator address at the same time frame
// operator occupies first 160 bits
uint256 operatorService = uint256(uint160(operator));
// serviceId occupies next 32 bits as serviceId is limited by the 2^32 - 1 value
operatorService |= serviceId << 160;
uint256 nonce = mapOperatorRegisterAgentsNonces[operatorService];
// Get register agents message hash
bytes32 msgHash = getRegisterAgentsHash(operator, serviceOwner, serviceId, agentInstances, agentIds, nonce);
// Verify the signed hash against the operator address
_verifySignedHash(operator, msgHash, signature);
// Update corresponding nonce value
nonce++;
mapOperatorRegisterAgentsNonces[operatorService] = nonce;
// Record the actual ERC20 bond
bool isTokenSecured =
IServiceTokenUtility(serviceRegistryTokenUtility).registerAgentsTokenDeposit(operator, serviceId, agentIds);
// Register agent instances in a main ServiceRegistry contract
if (isTokenSecured) {
// If the service Id is based on the ERC20 token, the provided value to the standard registration is 1
// multiplied by the number of agent instances
success = IService(serviceRegistry).registerAgents{value: agentInstances.length * BOND_WRAPPER}(
operator, serviceId, agentInstances, agentIds
);
} else {
// Otherwise follow the standard msg.value path
success = IService(serviceRegistry).registerAgents{value: msg.value}(
operator, serviceId, agentInstances, agentIds
);
}
emit RegisterAgentsWithSignatureExecuted(operator, serviceId, agentInstances, agentIds);
_locked = 1;
}
}