-
Notifications
You must be signed in to change notification settings - Fork 2
/
ServiceRegistryL2.sol
880 lines (777 loc) · 37.5 KB
/
ServiceRegistryL2.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
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
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./GenericRegistry.sol";
import "./interfaces/IMultisig.sol";
// This struct is 128 bits in total
struct AgentParams {
// Number of agent instances. This number is limited by the number of agent instances
uint32 slots;
// Bond per agent instance. This is enough for 1b+ ETH or 1e27
uint96 bond;
}
// This struct is 192 bits in total
struct AgentInstance {
// Address of an agent instance
address instance;
// Canonical agent Id. This number is limited by the max number of agent Ids (see UnitRegistry contract)
uint32 agentId;
}
/// @title Service Registry L2 - Smart contract for registering services on L2 chains.
/// @dev Underlying canonical agents and components are not checked for their validity since they are set up on the L1 mainnet.
/// The architecture is optimistic, in the sense that service owners are assumed to reference existing and relevant agents.
/// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
contract ServiceRegistryL2 is GenericRegistry {
event DrainerUpdated(address indexed drainer);
event Deposit(address indexed sender, uint256 amount);
event Refund(address indexed receiver, uint256 amount);
event CreateService(uint256 indexed serviceId, bytes32 configHash);
event UpdateService(uint256 indexed serviceId, bytes32 configHash);
event RegisterInstance(address indexed operator, uint256 indexed serviceId, address indexed agentInstance, uint256 agentId);
event CreateMultisigWithAgents(uint256 indexed serviceId, address indexed multisig);
event ActivateRegistration(uint256 indexed serviceId);
event TerminateService(uint256 indexed serviceId);
event OperatorSlashed(uint256 amount, address indexed operator, uint256 indexed serviceId);
event OperatorUnbond(address indexed operator, uint256 indexed serviceId);
event DeployService(uint256 indexed serviceId);
event Drain(address indexed drainer, uint256 amount);
enum ServiceState {
NonExistent,
PreRegistration,
ActiveRegistration,
FinishedRegistration,
Deployed,
TerminatedBonded
}
// Service parameters
struct Service {
// Registration activation deposit
// This is enough for 1b+ ETH or 1e27
uint96 securityDeposit;
// Multisig address for agent instances
address multisig;
// IPFS hashes pointing to the config metadata
bytes32 configHash;
// Agent instance signers threshold: must no less than ceil((n * 2 + 1) / 3) of all the agent instances combined
// This number will be enough to have ((2^32 - 1) * 3 - 1) / 2, which is bigger than 6.44b
uint32 threshold;
// Total number of agent instances. We assume that the number of instances is bounded by 2^32 - 1
uint32 maxNumAgentInstances;
// Actual number of agent instances. This number is less or equal to maxNumAgentInstances
uint32 numAgentInstances;
// Service state
ServiceState state;
// Canonical agent Ids for the service. Individual agent Id is bounded by the max number of agent Id
uint32[] agentIds;
}
// The amount of funds slashed. This is enough for 1b+ ETH or 1e27
uint96 public slashedFunds;
// Drainer address: set by the government and is allowed to drain ETH funds accumulated in this contract
address public drainer;
// Service registry version number
string public constant VERSION = "1.0.0";
// Map of service Id => set of IPFS hashes pointing to the config metadata
mapping (uint256 => bytes32[]) public mapConfigHashes;
// Map of operator address and serviceId => set of registered agent instance addresses
mapping(uint256 => AgentInstance[]) public mapOperatorAndServiceIdAgentInstances;
// Service Id and canonical agent Id => number of agent instances and correspondent instance registration bond
mapping(uint256 => AgentParams) public mapServiceAndAgentIdAgentParams;
// Actual agent instance addresses. Service Id and canonical agent Id => Set of agent instance addresses.
mapping(uint256 => address[]) public mapServiceAndAgentIdAgentInstances;
// Map of operator address and serviceId => agent instance bonding / escrow balance
mapping(uint256 => uint96) public mapOperatorAndServiceIdOperatorBalances;
// Map of agent instance address => service id it is registered with and operator address that supplied the instance
mapping (address => address) public mapAgentInstanceOperators;
// Map of policy for multisig implementations
mapping (address => bool) public mapMultisigs;
// Map of service counter => service
mapping (uint256 => Service) public mapServices;
/// @dev Service registry constructor.
/// @param _name Service contract name.
/// @param _symbol Agent contract symbol.
/// @param _baseURI Agent registry token base URI.
constructor(string memory _name, string memory _symbol, string memory _baseURI)
ERC721(_name, _symbol)
{
baseURI = _baseURI;
owner = msg.sender;
}
/// @dev Changes the drainer.
/// @param newDrainer Address of a drainer.
function changeDrainer(address newDrainer) external {
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero address
if (newDrainer == address(0)) {
revert ZeroAddress();
}
drainer = newDrainer;
emit DrainerUpdated(newDrainer);
}
/// @dev Going through basic initial service checks.
/// @param configHash IPFS hash pointing to the config metadata.
/// @param agentIds Canonical agent Ids.
/// @param agentParams Number of agent instances and required required bond to register an instance in the service.
function _initialChecks(
bytes32 configHash,
uint32[] memory agentIds,
AgentParams[] memory agentParams
) private pure
{
// Check for the non-zero hash value
if (configHash == 0) {
revert ZeroValue();
}
// Checking for non-empty arrays and correct number of values in them
if (agentIds.length == 0 || agentIds.length != agentParams.length) {
revert WrongArrayLength(agentIds.length, agentParams.length);
}
// Check for duplicate canonical agent Ids
uint256 lastId;
for (uint256 i = 0; i < agentIds.length; i++) {
if (agentIds[i] < (lastId + 1)) {
revert WrongAgentId(agentIds[i]);
}
lastId = agentIds[i];
}
}
/// @dev Sets the service data.
/// @param service A service instance to fill the data for.
/// @param agentIds Canonical agent Ids.
/// @param agentParams Number of agent instances and required required bond to register an instance in the service.
/// @param size Size of a canonical agent ids set.
/// @param serviceId ServiceId.
function _setServiceData(
Service memory service,
uint32[] memory agentIds,
AgentParams[] memory agentParams,
uint256 size,
uint256 serviceId
) private
{
// Security deposit
uint96 securityDeposit;
// Add canonical agent Ids for the service and the slots map
service.agentIds = new uint32[](size);
for (uint256 i = 0; i < size; i++) {
service.agentIds[i] = agentIds[i];
// Push a pair of key defining variables into one key. Service or agent Ids are not enough by themselves
// As with other units, we assume that the system is not expected to support more than than 2^32-1 services
// Need to carefully check pairings, since it's hard to find if something is incorrectly misplaced bitwise
// serviceId occupies first 32 bits
uint256 serviceAgent = serviceId;
// agentId takes the second 32 bits
serviceAgent |= uint256(agentIds[i]) << 32;
mapServiceAndAgentIdAgentParams[serviceAgent] = agentParams[i];
service.maxNumAgentInstances += agentParams[i].slots;
// Security deposit is the maximum of the canonical agent registration bond
if (agentParams[i].bond > securityDeposit) {
securityDeposit = agentParams[i].bond;
}
}
service.securityDeposit = securityDeposit;
// Check for the correct threshold: no less than ceil((n * 2 + 1) / 3) of all the agent instances combined
uint256 checkThreshold = uint256(service.maxNumAgentInstances * 2 + 1);
if (checkThreshold % 3 == 0) {
checkThreshold = checkThreshold / 3;
} else {
checkThreshold = checkThreshold / 3 + 1;
}
if (service.threshold < checkThreshold || service.threshold > service.maxNumAgentInstances) {
revert WrongThreshold(service.threshold, checkThreshold, service.maxNumAgentInstances);
}
}
/// @dev Creates a new service.
/// @param serviceOwner Individual that creates and controls a service.
/// @param configHash IPFS hash pointing to the config metadata.
/// @param agentIds Canonical agent Ids in a sorted ascending order.
/// @notice If agentIds are not sorted in ascending order then the function that executes initial checks gets reverted.
/// @param agentParams Number of agent instances and required required bond to register an instance in the service.
/// @param threshold Signers threshold for a multisig composed by agent instances.
/// @return serviceId Created service Id.
function create(
address serviceOwner,
bytes32 configHash,
uint32[] memory agentIds,
AgentParams[] memory agentParams,
uint32 threshold
) external returns (uint256 serviceId)
{
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Check for the non-empty service owner address
if (serviceOwner == address(0)) {
revert ZeroAddress();
}
// Execute initial checks
_initialChecks(configHash, agentIds, agentParams);
// Check that there are no zero number of slots for a specific canonical agent id and no zero registration bond
for (uint256 i = 0; i < agentIds.length; i++) {
if (agentParams[i].slots == 0 || agentParams[i].bond == 0) {
revert ZeroValue();
}
}
// Create a new service Id
serviceId = totalSupply;
serviceId++;
// Set high-level data components of the service instance
Service memory service;
// Updating high-level data components of the service
service.threshold = threshold;
// Assigning the initial hash
service.configHash = configHash;
// Set the initial service state
service.state = ServiceState.PreRegistration;
// Set service data
_setServiceData(service, agentIds, agentParams, agentIds.length, serviceId);
mapServices[serviceId] = service;
totalSupply = serviceId;
// Mint the service instance to the service owner and record the service structure
_safeMint(serviceOwner, serviceId);
emit CreateService(serviceId, configHash);
_locked = 1;
}
/// @dev Updates a service in a CRUD way.
/// @param serviceOwner Individual that creates and controls a service.
/// @param configHash IPFS hash pointing to the config metadata.
/// @param agentIds Canonical agent Ids in a sorted ascending order.
/// @notice If agentIds are not sorted in ascending order then the function that executes initial checks gets reverted.
/// @param agentParams Number of agent instances and required required bond to register an instance in the service.
/// @param threshold Signers threshold for a multisig composed by agent instances.
/// @param serviceId Service Id to be updated.
/// @return success True, if function executed successfully.
function update(
address serviceOwner,
bytes32 configHash,
uint32[] memory agentIds,
AgentParams[] memory agentParams,
uint32 threshold,
uint256 serviceId
) external returns (bool success)
{
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Check for the service ownership
address actualOwner = ownerOf(serviceId);
if (actualOwner != serviceOwner) {
revert OwnerOnly(serviceOwner, actualOwner);
}
Service memory service = mapServices[serviceId];
if (service.state != ServiceState.PreRegistration) {
revert WrongServiceState(uint256(service.state), serviceId);
}
// Execute initial checks
_initialChecks(configHash, agentIds, agentParams);
// Updating high-level data components of the service
service.threshold = threshold;
service.maxNumAgentInstances = 0;
// Collect non-zero canonical agent ids and slots / costs, remove any canonical agent Ids from the params map
uint32[] memory newAgentIds = new uint32[](agentIds.length);
AgentParams[] memory newAgentParams = new AgentParams[](agentIds.length);
uint256 size;
for (uint256 i = 0; i < agentIds.length; i++) {
if (agentParams[i].slots == 0) {
// Push a pair of key defining variables into one key. Service or agent Ids are not enough by themselves
// serviceId occupies first 32 bits, agentId gets the next 32 bits
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(agentIds[i]) << 32;
delete mapServiceAndAgentIdAgentParams[serviceAgent];
} else {
newAgentIds[size] = agentIds[i];
newAgentParams[size] = agentParams[i];
size++;
}
}
// Check if the previous hash is the same / hash was not updated
bytes32 lastConfigHash = service.configHash;
if (lastConfigHash != configHash) {
mapConfigHashes[serviceId].push(lastConfigHash);
service.configHash = configHash;
}
// Set service data and record the modified service struct
_setServiceData(service, newAgentIds, newAgentParams, size, serviceId);
mapServices[serviceId] = service;
emit UpdateService(serviceId, configHash);
success = true;
}
/// @dev Activates the service.
/// @param serviceOwner Individual that creates and controls a service.
/// @param serviceId Correspondent service Id.
/// @return success True, if function executed successfully.
function activateRegistration(address serviceOwner, uint256 serviceId) external payable returns (bool success)
{
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Check for the service ownership
address actualOwner = ownerOf(serviceId);
if (actualOwner != serviceOwner) {
revert OwnerOnly(serviceOwner, actualOwner);
}
Service storage service = mapServices[serviceId];
// Service must be inactive
if (service.state != ServiceState.PreRegistration) {
revert ServiceMustBeInactive(serviceId);
}
if (msg.value != service.securityDeposit) {
revert IncorrectRegistrationDepositValue(msg.value, service.securityDeposit, serviceId);
}
// Activate the agent instance registration
service.state = ServiceState.ActiveRegistration;
emit ActivateRegistration(serviceId);
success = true;
}
/// @dev Registers agent instances.
/// @param operator Address of the operator.
/// @param serviceId Service Id to register agent instances for.
/// @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(
address operator,
uint256 serviceId,
address[] memory agentInstances,
uint32[] memory agentIds
) external payable returns (bool success)
{
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Check if the length of canonical agent instance addresses array and ids array have the same length
if (agentInstances.length != agentIds.length) {
revert WrongArrayLength(agentInstances.length, agentIds.length);
}
Service storage service = mapServices[serviceId];
// The service has to be active to register agents
if (service.state != ServiceState.ActiveRegistration) {
revert WrongServiceState(uint256(service.state), serviceId);
}
// Check for the sufficient amount of bond fee is provided
uint256 numAgents = agentInstances.length;
uint256 totalBond = 0;
for (uint256 i = 0; i < numAgents; ++i) {
// Check if canonical agent Id exists in the service
// Push a pair of key defining variables into one key. Service or agent Ids are not enough by themselves
// serviceId occupies first 32 bits, agentId gets the next 32 bits
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(agentIds[i]) << 32;
AgentParams memory agentParams = mapServiceAndAgentIdAgentParams[serviceAgent];
if (agentParams.slots == 0) {
revert AgentNotInService(agentIds[i], serviceId);
}
totalBond += agentParams.bond;
}
if (msg.value != totalBond) {
revert IncorrectAgentBondingValue(msg.value, totalBond, serviceId);
}
// Operator address must not be used as an agent instance anywhere else
if (mapAgentInstanceOperators[operator] != address(0)) {
revert WrongOperator(serviceId);
}
// Push a pair of key defining variables into one key. Service Id or operator are not enough by themselves
// operator occupies first 160 bits
uint256 operatorService = uint256(uint160(operator));
// serviceId occupies next 32 bits assuming it is not greater than 2^32 - 1 in value
operatorService |= serviceId << 160;
for (uint256 i = 0; i < numAgents; ++i) {
address agentInstance = agentInstances[i];
uint32 agentId = agentIds[i];
// Operator address must be different from agent instance one
if (operator == agentInstance) {
revert WrongOperator(serviceId);
}
// Check if the agent instance is already engaged with another service
if (mapAgentInstanceOperators[agentInstance] != address(0)) {
revert AgentInstanceRegistered(mapAgentInstanceOperators[agentInstance]);
}
// Check if there is an empty slot for the agent instance in this specific service
// serviceId occupies first 32 bits, agentId gets the next 32 bits
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(agentIds[i]) << 32;
if (mapServiceAndAgentIdAgentInstances[serviceAgent].length == mapServiceAndAgentIdAgentParams[serviceAgent].slots) {
revert AgentInstancesSlotsFilled(serviceId);
}
// Add agent instance and operator and set the instance engagement
mapServiceAndAgentIdAgentInstances[serviceAgent].push(agentInstance);
mapOperatorAndServiceIdAgentInstances[operatorService].push(AgentInstance(agentInstance, agentId));
service.numAgentInstances++;
mapAgentInstanceOperators[agentInstance] = operator;
emit RegisterInstance(operator, serviceId, agentInstance, agentId);
}
// If the service agent instance capacity is reached, the service becomes finished-registration
if (service.numAgentInstances == service.maxNumAgentInstances) {
service.state = ServiceState.FinishedRegistration;
}
// Update operator's bonding balance
mapOperatorAndServiceIdOperatorBalances[operatorService] += uint96(msg.value);
emit Deposit(operator, msg.value);
success = true;
}
/// @dev Creates multisig instance controlled by the set of service agent instances and deploys the service.
/// @param serviceOwner Individual that creates and controls a 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(
address serviceOwner,
uint256 serviceId,
address multisigImplementation,
bytes memory data
) external returns (address multisig)
{
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Check for the service ownership
address actualOwner = ownerOf(serviceId);
if (actualOwner != serviceOwner) {
revert OwnerOnly(serviceOwner, actualOwner);
}
// Check for the whitelisted multisig implementation
if (!mapMultisigs[multisigImplementation]) {
revert UnauthorizedMultisig(multisigImplementation);
}
Service storage service = mapServices[serviceId];
if (service.state != ServiceState.FinishedRegistration) {
revert WrongServiceState(uint256(service.state), serviceId);
}
// Get all agent instances for the multisig
address[] memory agentInstances = _getAgentInstances(service, serviceId);
// Create a multisig with agent instances
multisig = IMultisig(multisigImplementation).create(agentInstances, service.threshold, data);
service.multisig = multisig;
service.state = ServiceState.Deployed;
emit CreateMultisigWithAgents(serviceId, multisig);
emit DeployService(serviceId);
_locked = 1;
}
/// @dev Slashes a specified agent instance.
/// @param agentInstances Agent instances to slash.
/// @param amounts Correspondent amounts to slash.
/// @param serviceId Service Id.
/// @return success True, if function executed successfully.
function slash(address[] memory agentInstances, uint96[] memory amounts, uint256 serviceId) external
returns (bool success)
{
// Check if the service is deployed
// Since we do not kill (burn) services, we want this check to happen in a right service state.
// If the service is deployed, it definitely exists and is running. We do not want this function to be abused
// when the service was deployed, then terminated, then in a sleep mode or before next deployment somebody
// could use this function and try to slash operators.
Service memory service = mapServices[serviceId];
if (service.state != ServiceState.Deployed) {
revert WrongServiceState(uint256(service.state), serviceId);
}
// Check for the array size
if (agentInstances.length != amounts.length) {
revert WrongArrayLength(agentInstances.length, amounts.length);
}
// Only the multisig of a correspondent address can slash its agent instances
if (msg.sender != service.multisig) {
revert OnlyOwnServiceMultisig(msg.sender, service.multisig, serviceId);
}
// Loop over each agent instance
uint256 numInstancesToSlash = agentInstances.length;
for (uint256 i = 0; i < numInstancesToSlash; ++i) {
// Get the service Id from the agentInstance map
address operator = mapAgentInstanceOperators[agentInstances[i]];
// Push a pair of key defining variables into one key. Service Id or operator are not enough by themselves
// operator occupies first 160 bits
uint256 operatorService = uint256(uint160(operator));
// serviceId occupies next 32 bits
operatorService |= serviceId << 160;
// Slash the balance of the operator, make sure it does not go below zero
uint96 balance = mapOperatorAndServiceIdOperatorBalances[operatorService];
if ((amounts[i] + 1) > balance) {
// We cannot add to the slashed amount more than the balance of the operator
slashedFunds += balance;
balance = 0;
} else {
slashedFunds += amounts[i];
balance -= amounts[i];
}
mapOperatorAndServiceIdOperatorBalances[operatorService] = balance;
emit OperatorSlashed(amounts[i], operator, serviceId);
}
success = true;
}
/// @dev Terminates the service.
/// @param serviceOwner Owner of the service.
/// @param serviceId Service Id to be updated.
/// @return success True, if function executed successfully.
/// @return refund Refund to return to the service owner.
function terminate(address serviceOwner, uint256 serviceId) external returns (bool success, uint256 refund)
{
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Check for the service ownership
address actualOwner = ownerOf(serviceId);
if (actualOwner != serviceOwner) {
revert OwnerOnly(serviceOwner, actualOwner);
}
Service storage service = mapServices[serviceId];
// Check if the service is already terminated
if (service.state == ServiceState.PreRegistration || service.state == ServiceState.TerminatedBonded) {
revert WrongServiceState(uint256(service.state), serviceId);
}
// Define the state of the service depending on the number of bonded agent instances
if (service.numAgentInstances > 0) {
service.state = ServiceState.TerminatedBonded;
} else {
service.state = ServiceState.PreRegistration;
}
// Delete the sensitive data
for (uint256 i = 0; i < service.agentIds.length; ++i) {
// serviceId occupies first 32 bits, agentId gets the next 32 bits
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(service.agentIds[i]) << 32;
delete mapServiceAndAgentIdAgentInstances[serviceAgent];
}
// Return registration deposit back to the service owner
refund = service.securityDeposit;
// By design, the refund is always a non-zero value, so no check is needed here fo that
(bool result, ) = serviceOwner.call{value: refund}("");
if (!result) {
revert TransferFailed(address(0), address(this), serviceOwner, refund);
}
emit Refund(serviceOwner, refund);
emit TerminateService(serviceId);
success = true;
_locked = 1;
}
/// @dev Unbonds agent instances of the operator from the service.
/// @param operator Operator of agent instances.
/// @param serviceId Service Id.
/// @return success True, if function executed successfully.
/// @return refund The amount of refund returned to the operator.
function unbond(address operator, uint256 serviceId) external returns (bool success, uint256 refund) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the manager privilege for a service management
if (manager != msg.sender) {
revert ManagerOnly(msg.sender, manager);
}
// Checks if the operator address is not zero
if (operator == address(0)) {
revert ZeroAddress();
}
Service storage service = mapServices[serviceId];
// Service can only be in the terminated-bonded state or expired-registration in order to proceed
if (service.state != ServiceState.TerminatedBonded) {
revert WrongServiceState(uint256(service.state), serviceId);
}
// Check for the operator and unbond all its agent instances
// Push a pair of key defining variables into one key. Service Id or operator are not enough by themselves
// operator occupies first 160 bits
uint256 operatorService = uint256(uint160(operator));
// serviceId occupies next 32 bits
operatorService |= serviceId << 160;
AgentInstance[] memory agentInstances = mapOperatorAndServiceIdAgentInstances[operatorService];
uint256 numAgentsUnbond = agentInstances.length;
if (numAgentsUnbond == 0) {
revert OperatorHasNoInstances(operator, serviceId);
}
// Subtract number of unbonded agent instances
service.numAgentInstances -= uint32(numAgentsUnbond);
// When number of instances is equal to zero, all the operators have unbonded and the service is moved into
// the PreRegistration state, from where it can be updated / start registration / get deployed again
if (service.numAgentInstances == 0) {
service.state = ServiceState.PreRegistration;
}
// else condition is redundant here, since the service is either in the TerminatedBonded state, or moved
// into the PreRegistration state and unbonding is not possible before the new TerminatedBonded state is reached
// Calculate registration refund and free all agent instances
for (uint256 i = 0; i < numAgentsUnbond; i++) {
// serviceId occupies first 32 bits, agentId gets the next 32 bits
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(agentInstances[i].agentId) << 32;
refund += mapServiceAndAgentIdAgentParams[serviceAgent].bond;
// Clean-up the sensitive data such that it is not reused later
delete mapAgentInstanceOperators[agentInstances[i].instance];
}
// Clean all the operator agent instances records for this service
delete mapOperatorAndServiceIdAgentInstances[operatorService];
// Calculate the refund
uint96 balance = mapOperatorAndServiceIdOperatorBalances[operatorService];
// This situation is possible if the operator was slashed for the agent instance misbehavior
if (refund > balance) {
refund = balance;
}
// Refund the operator
if (refund > 0) {
// Operator's balance is essentially zero after the refund
mapOperatorAndServiceIdOperatorBalances[operatorService] = 0;
// Send the refund
(bool result, ) = operator.call{value: refund}("");
if (!result) {
revert TransferFailed(address(0), address(this), operator, refund);
}
emit Refund(operator, refund);
}
emit OperatorUnbond(operator, serviceId);
success = true;
_locked = 1;
}
/// @dev Gets the service instance.
/// @param serviceId Service Id.
/// @return service Corresponding Service struct.
function getService(uint256 serviceId) external view returns (Service memory service) {
service = mapServices[serviceId];
}
/// @dev Gets service agent parameters: number of agent instances (slots) and a bond amount.
/// @param serviceId Service Id.
/// @return numAgentIds Number of canonical agent Ids in the service.
/// @return agentParams Set of agent parameters for each canonical agent Id.
function getAgentParams(uint256 serviceId) external view
returns (uint256 numAgentIds, AgentParams[] memory agentParams)
{
Service memory service = mapServices[serviceId];
numAgentIds = service.agentIds.length;
agentParams = new AgentParams[](numAgentIds);
for (uint256 i = 0; i < numAgentIds; ++i) {
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(service.agentIds[i]) << 32;
agentParams[i] = mapServiceAndAgentIdAgentParams[serviceAgent];
}
}
/// @dev Lists all the instances of a given canonical agent Id if the service.
/// @param serviceId Service Id.
/// @param agentId Canonical agent Id.
/// @return numAgentInstances Number of agent instances.
/// @return agentInstances Set of agent instances for a specified canonical agent Id.
function getInstancesForAgentId(uint256 serviceId, uint256 agentId) external view
returns (uint256 numAgentInstances, address[] memory agentInstances)
{
uint256 serviceAgent = serviceId;
serviceAgent |= agentId << 32;
numAgentInstances = mapServiceAndAgentIdAgentInstances[serviceAgent].length;
agentInstances = new address[](numAgentInstances);
for (uint256 i = 0; i < numAgentInstances; i++) {
agentInstances[i] = mapServiceAndAgentIdAgentInstances[serviceAgent][i];
}
}
/// @dev Gets all agent instances.
/// @param service Service instance.
/// @param serviceId ServiceId.
/// @return agentInstances Pre-allocated list of agent instance addresses.
function _getAgentInstances(Service memory service, uint256 serviceId) private view
returns (address[] memory agentInstances)
{
agentInstances = new address[](service.numAgentInstances);
uint256 count;
for (uint256 i = 0; i < service.agentIds.length; i++) {
// serviceId occupies first 32 bits, agentId gets the next 32 bits
uint256 serviceAgent = serviceId;
serviceAgent |= uint256(service.agentIds[i]) << 32;
for (uint256 j = 0; j < mapServiceAndAgentIdAgentInstances[serviceAgent].length; j++) {
agentInstances[count] = mapServiceAndAgentIdAgentInstances[serviceAgent][j];
count++;
}
}
}
/// @dev Gets service agent instances.
/// @param serviceId ServiceId.
/// @return numAgentInstances Number of agent instances.
/// @return agentInstances Pre-allocated list of agent instance addresses.
function getAgentInstances(uint256 serviceId) external view
returns (uint256 numAgentInstances, address[] memory agentInstances)
{
Service memory service = mapServices[serviceId];
agentInstances = _getAgentInstances(service, serviceId);
numAgentInstances = agentInstances.length;
}
/// @dev Gets previous service config hashes.
/// @param serviceId Service Id.
/// @return numHashes Number of hashes.
/// @return configHashes The list of previous component hashes (excluding the current one).
function getPreviousHashes(uint256 serviceId) external view
returns (uint256 numHashes, bytes32[] memory configHashes)
{
configHashes = mapConfigHashes[serviceId];
numHashes = configHashes.length;
}
/// @dev Gets the operator's balance in a specific service.
/// @param operator Operator address.
/// @param serviceId Service Id.
/// @return balance The balance of the operator.
function getOperatorBalance(address operator, uint256 serviceId) external view returns (uint256 balance)
{
uint256 operatorService = uint256(uint160(operator));
operatorService |= serviceId << 160;
balance = mapOperatorAndServiceIdOperatorBalances[operatorService];
}
/// @dev Controls multisig implementation address permission.
/// @param multisig Address of a multisig implementation.
/// @param permission Grant or revoke permission.
/// @return success True, if function executed successfully.
function changeMultisigPermission(address multisig, bool permission) external returns (bool success) {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
if (multisig == address(0)) {
revert ZeroAddress();
}
mapMultisigs[multisig] = permission;
success = true;
}
/// @dev Drains slashed funds.
/// @return amount Drained amount.
function drain() external returns (uint256 amount) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the drainer address
if (msg.sender != drainer) {
revert ManagerOnly(msg.sender, drainer);
}
// Drain the slashed funds
amount = slashedFunds;
if (amount > 0) {
slashedFunds = 0;
// Send the refund
(bool result, ) = msg.sender.call{value: amount}("");
if (!result) {
revert TransferFailed(address(0), address(this), msg.sender, amount);
}
emit Drain(msg.sender, amount);
}
_locked = 1;
}
/// @dev Gets the hash of the service.
/// @param serviceId Service Id.
/// @return Service hash.
function _getUnitHash(uint256 serviceId) internal view override returns (bytes32) {
return mapServices[serviceId].configHash;
}
}