-
Notifications
You must be signed in to change notification settings - Fork 17
/
Tokenomics.sol
1482 lines (1328 loc) · 76.3 KB
/
Tokenomics.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
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {convert, UD60x18} from "@prb/math/src/UD60x18.sol";
import {TokenomicsConstants} from "./TokenomicsConstants.sol";
import {IDonatorBlacklist} from "./interfaces/IDonatorBlacklist.sol";
import {IErrorsTokenomics} from "./interfaces/IErrorsTokenomics.sol";
// IOLAS interface
interface IOLAS {
/// @dev Provides OLA token time launch.
/// @return Time launch.
function timeLaunch() external view returns (uint256);
}
// IERC721 token interface
interface IToken {
/// @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 Gets the total amount of tokens stored by the contract.
/// @return Amount of tokens.
function totalSupply() external view returns (uint256);
}
// ITreasury interface
interface ITreasury {
/// @dev Re-balances treasury funds to account for the treasury reward for a specific epoch.
/// @param treasuryRewards Treasury rewards.
/// @return success True, if the function execution is successful.
function rebalanceTreasury(uint256 treasuryRewards) external returns (bool success);
}
// IServiceRegistry interface.
interface IServiceRegistry {
enum UnitType {
Component,
Agent
}
/// @dev Checks if the service Id exists.
/// @param serviceId Service Id.
/// @return true if the service exists, false otherwise.
function exists(uint256 serviceId) external view returns (bool);
/// @dev Gets the full set of linearized components / canonical agent Ids for a specified service.
/// @notice The service must be / have been deployed in order to get the actual data.
/// @param serviceId Service Id.
/// @return numUnitIds Number of component / agent Ids.
/// @return unitIds Set of component / agent Ids.
function getUnitIdsOfService(UnitType unitType, uint256 serviceId) external view
returns (uint256 numUnitIds, uint32[] memory unitIds);
}
// IVotingEscrow interface
interface IVotingEscrow {
/// @dev Gets the voting power.
/// @param account Account address.
function getVotes(address account) external view returns (uint256);
}
/// @dev Only `manager` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param manager Required sender address as a manager.
error ManagerOnly(address sender, address manager);
/// @dev Only `owner` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param owner Required sender address as an owner.
error OwnerOnly(address sender, address owner);
/// @dev Provided zero address.
error ZeroAddress();
/// @dev Wrong length of two arrays.
/// @param numValues1 Number of values in a first array.
/// @param numValues2 Number of values in a second array.
error WrongArrayLength(uint256 numValues1, uint256 numValues2);
/// @dev Service Id does not exist in registry records.
/// @param serviceId Service Id.
error ServiceDoesNotExist(uint256 serviceId);
/// @dev Zero value when it has to be different from zero.
error ZeroValue();
/// @dev Value overflow.
/// @param provided Overflow value.
/// @param max Maximum possible value.
error Overflow(uint256 provided, uint256 max);
/// @dev Service was never deployed.
/// @param serviceId Service Id.
error ServiceNeverDeployed(uint256 serviceId);
/// @dev Received lower value than the expected one.
/// @param provided Provided value is lower.
/// @param expected Expected value.
error LowerThan(uint256 provided, uint256 expected);
/// @dev Wrong amount received / provided.
/// @param provided Provided amount.
/// @param expected Expected amount.
error WrongAmount(uint256 provided, uint256 expected);
/// @dev The donator address is blacklisted.
/// @param account Donator account address.
error DonatorBlacklisted(address account);
/// @dev The contract is already initialized.
error AlreadyInitialized();
/// @dev The contract has to be delegate-called via proxy.
error DelegatecallOnly();
/// @dev Caught an operation that is not supposed to happen in the same block.
error SameBlockNumberViolation();
/// @dev Failure of treasury re-balance during the reward allocation.
/// @param epochNumber Epoch number.
error TreasuryRebalanceFailed(uint256 epochNumber);
/// @dev Operation with a wrong component / agent Id.
/// @param unitId Component / agent Id.
/// @param unitType Type of the unit (component / agent).
error WrongUnitId(uint256 unitId, uint256 unitType);
/*
* In this contract we consider both ETH and OLAS tokens.
* For ETH tokens, there are currently about 121 million tokens.
* Even if the ETH inflation rate is 5% per year, it would take 130+ years to reach 2^96 - 1 of ETH total supply.
* Lately the inflation rate was lower and could actually be deflationary.
*
* For OLAS tokens, the initial numbers will be as follows:
* - For the first 10 years there will be the cap of 1 billion (1e27) tokens;
* - After 10 years, the inflation rate is capped at 2% per year.
* Starting from a year 11, the maximum number of tokens that can be reached per the year x is 1e27 * (1.02)^x.
* To make sure that a unit(n) does not overflow the total supply during the year x, we have to check that
* 2^n - 1 >= 1e27 * (1.02)^x. We limit n by 96, thus it would take 220+ years to reach that total supply.
*
* We then limit each time variable to last until the value of 2^32 - 1 in seconds.
* 2^32 - 1 gives 136+ years counted in seconds starting from the year 1970.
* Thus, this counter is safe until the year 2106.
*
* The number of blocks cannot be practically bigger than the number of seconds, since there is more than one second
* in a block. Thus, it is safe to assume that uint32 for the number of blocks is also sufficient.
*
* We also limit the number of registry units by the value of 2^32 - 1.
* We assume that the system is expected to support no more than 2^32-1 units.
*
* Lastly, we assume that the coefficients from tokenomics factors calculation are bound by 2^16 - 1.
*
* In conclusion, this contract is only safe to use until 2106.
*/
// Structure for component / agent point with tokenomics-related statistics
// The size of the struct is 96 + 32 + 8 * 2 = 144 (1 slot)
struct UnitPoint {
// Summation of all the relative OLAS top-ups accumulated by each component / agent in a service
// After 10 years, the OLAS inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1
uint96 sumUnitTopUpsOLAS;
// Number of new units
// This number cannot be practically bigger than the total number of supported units
uint32 numNewUnits;
// Reward component / agent fraction
// This number cannot be practically bigger than 100 as the summation with other fractions gives at most 100 (%)
uint8 rewardUnitFraction;
// Top-up component / agent fraction
// This number cannot be practically bigger than 100 as the summation with other fractions gives at most 100 (%)
uint8 topUpUnitFraction;
}
// Structure for epoch point with tokenomics-related statistics during each epoch
// The size of the struct is 96 * 2 + 64 + 32 * 2 + 8 * 2 = 256 + 80 (2 slots)
struct EpochPoint {
// Total amount of ETH donations accrued by the protocol during one epoch
// Even if the ETH inflation rate is 5% per year, it would take 130+ years to reach 2^96 - 1 of ETH total supply
uint96 totalDonationsETH;
// Amount of OLAS intended to fund top-ups for the epoch based on the inflation schedule
// After 10 years, the OLAS inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1
uint96 totalTopUpsOLAS;
// Inverse of the discount factor
// IDF is bound by a factor of 18, since (2^64 - 1) / 10^18 > 18
// IDF uses a multiplier of 10^18 by default, since it is a rational number and must be accounted for divisions
// The IDF depends on the epsilonRate value, idf = 1 + epsilonRate, and epsilonRate is bound by 17 with 18 decimals
uint64 idf;
// Number of new owners
// Each unit has at most one owner, so this number cannot be practically bigger than numNewUnits
uint32 numNewOwners;
// Epoch end timestamp
// 2^32 - 1 gives 136+ years counted in seconds starting from the year 1970, which is safe until the year of 2106
uint32 endTime;
// Parameters for rewards and top-ups (in percentage)
// Each of these numbers cannot be practically bigger than 100 as they sum up to 100%
// treasuryFraction + rewardComponentFraction + rewardAgentFraction = 100%
// Treasury fraction
uint8 rewardTreasuryFraction;
// maxBondFraction + topUpComponentFraction + topUpAgentFraction + stakingFraction <= 100%
// Amount of OLAS (in percentage of inflation) intended to fund bonding incentives during the epoch
uint8 maxBondFraction;
}
// Structure for tokenomics point
// The size of the struct is 256 * 2 + 256 * 2 = 256 * 4 (4 slots)
struct TokenomicsPoint {
// Two unit points in a representation of mapping and not on array to save on gas
// One unit point is for component (key = 0) and one is for agent (key = 1)
mapping(uint256 => UnitPoint) unitPoints;
// Epoch point
EpochPoint epochPoint;
}
// Struct for component / agent incentive balances
struct IncentiveBalances {
// Reward in ETH
// Even if the ETH inflation rate is 5% per year, it would take 130+ years to reach 2^96 - 1 of ETH total supply
uint96 reward;
// Pending relative reward in ETH
uint96 pendingRelativeReward;
// Top-up in OLAS
// After 10 years, the OLAS inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1
uint96 topUp;
// Pending relative top-up
uint96 pendingRelativeTopUp;
// Last epoch number the information was updated
// This number cannot be practically bigger than the number of blocks
uint32 lastEpoch;
}
// Struct for service staking epoch info
struct StakingPoint {
// Amount of OLAS that funds service staking incentives for the epoch based on the inflation schedule
// After 10 years, the OLAS inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1
uint96 stakingIncentive;
// Max allowed service staking incentive threshold
// This value is never bigger than the stakingIncentive
uint96 maxStakingIncentive;
// Service staking vote weighting threshold
// This number is bound by 10_000, ranging from 0 to 100% with the step of 0.01%
uint16 minStakingWeight;
// Service staking fraction
// This number cannot be practically bigger than 100 as it sums up to 100% with others
// maxBondFraction + topUpComponentFraction + topUpAgentFraction + stakingFraction <= 100%
uint8 stakingFraction;
}
/// @title Tokenomics - Smart contract for tokenomics logic with incentives for unit owners, discount factor
/// regulations for bonds, and staking incentives.
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract Tokenomics is TokenomicsConstants {
event OwnerUpdated(address indexed owner);
event TreasuryUpdated(address indexed treasury);
event DepositoryUpdated(address indexed depository);
event DispenserUpdated(address indexed dispenser);
event EpochLengthUpdated(uint256 epochLen);
event EffectiveBondUpdated(uint256 indexed epochNumber, uint256 effectiveBond);
event StakingRefunded(uint256 indexed epochNumber, uint256 amount);
event IDFUpdated(uint256 idf);
event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,
uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);
event TokenomicsParametersUpdated(uint256 indexed epochNumber);
event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,
uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction,
uint256 topUpAgentFraction, uint256 stakingFraction);
event StakingParamsUpdateRequested(uint256 indexed epochNumber, uint256 maxStakingIncentive,
uint256 minStakingWeight);
event IncentiveFractionsUpdated(uint256 indexed epochNumber);
event StakingParamsUpdated(uint256 indexed epochNumber);
event ComponentRegistryUpdated(address indexed componentRegistry);
event AgentRegistryUpdated(address indexed agentRegistry);
event ServiceRegistryUpdated(address indexed serviceRegistry);
event DonatorBlacklistUpdated(address indexed blacklist);
event EpochSettled(uint256 indexed epochCounter, uint256 treasuryRewards, uint256 accountRewards,
uint256 accountTopUps, uint256 effectiveBond, uint256 returnedStakingIncentive, uint256 totalStakingIncentive);
event TokenomicsImplementationUpdated(address indexed implementation);
// Owner address
address public owner;
// Max bond per epoch: calculated as a fraction from the OLAS inflation parameter
// After 10 years, the OLAS inflation rate is 2% per year. It would take 220+ years to reach 2^96 - 1
uint96 public maxBond;
// OLAS token address
address public olas;
// Inflation amount per second
uint96 public inflationPerSecond;
// Treasury contract address
address public treasury;
// veOLAS threshold for top-ups
// This number cannot be practically bigger than the number of OLAS tokens
uint96 public veOLASThreshold;
// Depository contract address
address public depository;
// effectiveBond = sum(MaxBond(e)) - sum(BondingProgram) over all epochs: accumulates leftovers from previous epochs
// Effective bond is updated before the start of the next epoch such that the bonding limits are accounted for
// This number cannot be practically bigger than the inflation remainder of OLAS
uint96 public effectiveBond;
// Dispenser contract address
address public dispenser;
// Number of units of useful code that can be built by a developer during one epoch
// We assume this number will not be practically bigger than 4,722 of its integer-part (with 18 digits of fractional-part)
uint72 public codePerDev;
// Current year number
// This number is enough for the next 255 years
uint8 public currentYear;
// Tokenomics parameters change request flag
bytes1 public tokenomicsParametersUpdated;
// Reentrancy lock
uint8 internal _locked;
// Component Registry
address public componentRegistry;
// Default epsilon rate that contributes to the interest rate: 10% or 0.1
// We assume that for the IDF calculation epsilonRate must be lower than 17 (with 18 decimals)
// (2^64 - 1) / 10^18 > 18, however IDF = 1 + epsilonRate, thus we limit epsilonRate by 17 with 18 decimals at most
uint64 public epsilonRate;
// Epoch length in seconds
// By design, the epoch length cannot be practically bigger than one year, or 31_536_000 seconds
uint32 public epochLen;
// Agent Registry
address public agentRegistry;
// veOLAS threshold for top-ups that will be set in the next epoch
// This number cannot be practically bigger than the number of OLAS tokens
uint96 public nextVeOLASThreshold;
// Service Registry
address public serviceRegistry;
// Global epoch counter
// This number cannot be practically bigger than the number of blocks
uint32 public epochCounter;
// Time launch of the OLAS contract
// 2^32 - 1 gives 136+ years counted in seconds starting from the year 1970, which is safe until the year of 2106
uint32 public timeLaunch;
// Epoch length in seconds that will be set in the next epoch
// By design, the epoch length cannot be practically bigger than one year, or 31_536_000 seconds
uint32 public nextEpochLen;
// Voting Escrow address
address public ve;
// Number of valuable devs that can be paid per units of capital per epoch in fixed point format
// We assume this number will not be practically bigger than 4,722 of its integer-part (with 18 digits of fractional-part)
uint72 public devsPerCapital;
// Blacklist contract address
address public donatorBlacklist;
// Last donation block number to prevent the flash loan attack
// This number cannot be practically bigger than the number of seconds
uint32 public lastDonationBlockNumber;
// Map of service Ids and their amounts in current epoch
mapping(uint256 => uint256) public mapServiceAmounts;
// Mapping of owner of component / agent address => reward amount (in ETH)
mapping(address => uint256) public mapOwnerRewards;
// Mapping of owner of component / agent address => top-up amount (in OLAS)
mapping(address => uint256) public mapOwnerTopUps;
// Mapping of epoch => tokenomics point
mapping(uint256 => TokenomicsPoint) public mapEpochTokenomics;
// Map of new component / agent Ids that contribute to protocol owned services
mapping(uint256 => mapping(uint256 => bool)) public mapNewUnits;
// Mapping of new owner of component / agent addresses that create them
mapping(address => bool) public mapNewOwners;
// Mapping of component / agent Id => incentive balances
mapping(uint256 => mapping(uint256 => IncentiveBalances)) public mapUnitIncentives;
// Mapping of epoch => service staking point
mapping(uint256 => StakingPoint) public mapEpochStakingPoints;
/// @dev Tokenomics constructor.
constructor()
TokenomicsConstants()
{}
/// @dev Tokenomics initializer.
/// @notice Tokenomics contract must be initialized no later than one year from the launch of the OLAS token contract.
/// @param _olas OLAS token address.
/// @param _treasury Treasury address.
/// @param _depository Depository address.
/// @param _dispenser Dispenser address.
/// @param _ve Voting Escrow address.
/// @param _epochLen Epoch length.
/// @param _componentRegistry Component registry address.
/// @param _agentRegistry Agent registry address.
/// @param _serviceRegistry Service registry address.
/// @param _donatorBlacklist DonatorBlacklist address.
/// #if_succeeds {:msg "ep is correct endTime"} mapEpochTokenomics[0].epochPoint.endTime > 0;
/// #if_succeeds {:msg "maxBond eq effectiveBond form start"} effectiveBond == maxBond;
/// #if_succeeds {:msg "olas must not be a zero address"} old(_olas) != address(0) ==> olas == _olas;
/// #if_succeeds {:msg "treasury must not be a zero address"} old(_treasury) != address(0) ==> treasury == _treasury;
/// #if_succeeds {:msg "depository must not be a zero address"} old(_depository) != address(0) ==> depository == _depository;
/// #if_succeeds {:msg "dispenser must not be a zero address"} old(_dispenser) != address(0) ==> dispenser == _dispenser;
/// #if_succeeds {:msg "vaOLAS must not be a zero address"} old(_ve) != address(0) ==> ve == _ve;
/// #if_succeeds {:msg "epochLen"} old(_epochLen > MIN_EPOCH_LENGTH && _epochLen <= type(uint32).max) ==> epochLen == _epochLen;
/// #if_succeeds {:msg "componentRegistry must not be a zero address"} old(_componentRegistry) != address(0) ==> componentRegistry == _componentRegistry;
/// #if_succeeds {:msg "agentRegistry must not be a zero address"} old(_agentRegistry) != address(0) ==> agentRegistry == _agentRegistry;
/// #if_succeeds {:msg "serviceRegistry must not be a zero address"} old(_serviceRegistry) != address(0) ==> serviceRegistry == _serviceRegistry;
/// #if_succeeds {:msg "donatorBlacklist assignment"} donatorBlacklist == _donatorBlacklist;
/// #if_succeeds {:msg "inflationPerSecond must not be zero"} inflationPerSecond > 0 && inflationPerSecond <= getInflationForYear(0);
/// #if_succeeds {:msg "Zero epoch point end time must be non-zero"} mapEpochTokenomics[0].epochPoint.endTime > 0;
/// #if_succeeds {:msg "maxBond"} old(_epochLen > MIN_EPOCH_LENGTH && _epochLen <= type(uint32).max && inflationPerSecond > 0 && inflationPerSecond <= getInflationForYear(0))
/// ==> maxBond == (inflationPerSecond * _epochLen * mapEpochTokenomics[1].epochPoint.maxBondFraction) / 100;
function initializeTokenomics(
address _olas,
address _treasury,
address _depository,
address _dispenser,
address _ve,
uint256 _epochLen,
address _componentRegistry,
address _agentRegistry,
address _serviceRegistry,
address _donatorBlacklist
) external {
// Check if the contract is already initialized
if (owner != address(0)) {
revert AlreadyInitialized();
}
// Check for at least one zero contract address
if (_olas == address(0) || _treasury == address(0) || _depository == address(0) || _dispenser == address(0) ||
_ve == address(0) || _componentRegistry == address(0) || _agentRegistry == address(0) ||
_serviceRegistry == address(0)) {
revert ZeroAddress();
}
// Initialize storage variables
owner = msg.sender;
_locked = 1;
epsilonRate = 1e17;
veOLASThreshold = 10_000e18;
// Check that the epoch length has at least a practical minimal value
if (uint32(_epochLen) < MIN_EPOCH_LENGTH) {
revert LowerThan(_epochLen, MIN_EPOCH_LENGTH);
}
// Check that the epoch length is not out of defined bounds
if (uint32(_epochLen) > MAX_EPOCH_LENGTH) {
revert Overflow(_epochLen, MAX_EPOCH_LENGTH);
}
// Assign other input variables
olas = _olas;
treasury = _treasury;
depository = _depository;
dispenser = _dispenser;
ve = _ve;
epochLen = uint32(_epochLen);
componentRegistry = _componentRegistry;
agentRegistry = _agentRegistry;
serviceRegistry = _serviceRegistry;
donatorBlacklist = _donatorBlacklist;
// Time launch of the OLAS contract
uint256 _timeLaunch = IOLAS(_olas).timeLaunch();
// Check that the tokenomics contract is initialized no later than one year after the OLAS token is deployed
if (block.timestamp >= (_timeLaunch + ONE_YEAR)) {
revert Overflow(_timeLaunch + ONE_YEAR, block.timestamp);
}
// Seconds left in the deployment year for the zero year inflation schedule
// This value is necessary since it is different from a precise one year time, as the OLAS contract started earlier
uint256 zeroYearSecondsLeft = uint32(_timeLaunch + ONE_YEAR - block.timestamp);
// Calculating initial inflation per second: (mintable OLAS from getInflationForYear(0)) / (seconds left in a year)
// Note that we lose precision here dividing by the number of seconds right away, but to avoid complex calculations
// later we consider it less error-prone and sacrifice at most 6 insignificant digits (or 1e-12) of OLAS per year
uint256 _inflationPerSecond = getInflationForYear(0) / zeroYearSecondsLeft;
inflationPerSecond = uint96(_inflationPerSecond);
timeLaunch = uint32(_timeLaunch);
// The initial epoch start time is the end time of the zero epoch
mapEpochTokenomics[0].epochPoint.endTime = uint32(block.timestamp);
// The epoch counter starts from 1
epochCounter = 1;
TokenomicsPoint storage tp = mapEpochTokenomics[1];
// Setting initial parameters and fractions
devsPerCapital = 1e18;
tp.epochPoint.idf = 1e18;
// Reward fractions
// 0 stands for components and 1 for agents
tp.unitPoints[0].rewardUnitFraction = 83;
tp.unitPoints[1].rewardUnitFraction = 17;
// tp.epochPoint.rewardTreasuryFraction is essentially equal to zero
// We consider a unit of code as n agents or m components.
// Initially we consider 1 unit of code as either 2 agents or 1 component.
// E.g. if we have 2 profitable components and 2 profitable agents, this means there are (2 x 2.0 + 2 x 1.0) / 3 = 2
// units of code.
// We assume that during one epoch the developer can contribute with one piece of code (1 component or 2 agents)
codePerDev = 1e18;
// Top-up fractions
uint256 _maxBondFraction = 50;
tp.epochPoint.maxBondFraction = uint8(_maxBondFraction);
tp.unitPoints[0].topUpUnitFraction = 41;
tp.unitPoints[1].topUpUnitFraction = 9;
// Calculate initial effectiveBond based on the maxBond during the first epoch
// maxBond = inflationPerSecond * epochLen * maxBondFraction / 100
uint256 _maxBond = (_inflationPerSecond * _epochLen * _maxBondFraction) / 100;
maxBond = uint96(_maxBond);
effectiveBond = uint96(_maxBond);
}
/// @dev Gets the tokenomics implementation contract address.
/// @return implementation Tokenomics implementation contract address.
function tokenomicsImplementation() external view returns (address implementation) {
assembly {
implementation := sload(PROXY_TOKENOMICS)
}
}
/// @dev Changes the tokenomics implementation contract address.
/// @notice Make sure the implementation contract has a function to change the implementation.
/// @param implementation Tokenomics implementation contract address.
/// #if_succeeds {:msg "new implementation"} implementation == tokenomicsImplementation();
function changeTokenomicsImplementation(address implementation) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero address
if (implementation == address(0)) {
revert ZeroAddress();
}
// Store the implementation address under the designated storage slot
assembly {
sstore(PROXY_TOKENOMICS, implementation)
}
emit TokenomicsImplementationUpdated(implementation);
}
/// @dev Changes the owner address.
/// @param newOwner Address of a new owner.
function changeOwner(address newOwner) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero address
if (newOwner == address(0)) {
revert ZeroAddress();
}
owner = newOwner;
emit OwnerUpdated(newOwner);
}
/// @dev Changes various managing contract addresses.
/// @param _treasury Treasury address.
/// @param _depository Depository address.
/// @param _dispenser Dispenser address.
function changeManagers(address _treasury, address _depository, address _dispenser) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Change Treasury contract address
if (_treasury != address(0)) {
treasury = _treasury;
emit TreasuryUpdated(_treasury);
}
// Change Depository contract address
if (_depository != address(0)) {
depository = _depository;
emit DepositoryUpdated(_depository);
}
// Change Dispenser contract address
if (_dispenser != address(0)) {
dispenser = _dispenser;
emit DispenserUpdated(_dispenser);
}
}
/// @dev Changes registries contract addresses.
/// @param _componentRegistry Component registry address.
/// @param _agentRegistry Agent registry address.
/// @param _serviceRegistry Service registry address.
function changeRegistries(address _componentRegistry, address _agentRegistry, address _serviceRegistry) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for registries addresses
if (_componentRegistry != address(0)) {
componentRegistry = _componentRegistry;
emit ComponentRegistryUpdated(_componentRegistry);
}
if (_agentRegistry != address(0)) {
agentRegistry = _agentRegistry;
emit AgentRegistryUpdated(_agentRegistry);
}
if (_serviceRegistry != address(0)) {
serviceRegistry = _serviceRegistry;
emit ServiceRegistryUpdated(_serviceRegistry);
}
}
/// @dev Changes donator blacklist contract address.
/// @notice DonatorBlacklist contract can be disabled by setting its address to zero.
/// @param _donatorBlacklist DonatorBlacklist contract address.
function changeDonatorBlacklist(address _donatorBlacklist) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
donatorBlacklist = _donatorBlacklist;
emit DonatorBlacklistUpdated(_donatorBlacklist);
}
/// @dev Changes tokenomics parameters.
/// @notice Parameter values are not updated for those that are passed as zero or out of defined bounds.
/// @param _devsPerCapital Number of valuable devs can be paid per units of capital per epoch.
/// @param _codePerDev Number of units of useful code that can be built by a developer during one epoch.
/// @param _epsilonRate Epsilon rate that contributes to the interest rate value.
/// @param _epochLen New epoch length.
/// #if_succeeds {:msg "ep is correct endTime"} epochCounter > 1
/// ==> mapEpochTokenomics[epochCounter - 1].epochPoint.endTime > mapEpochTokenomics[epochCounter - 2].epochPoint.endTime;
/// #if_succeeds {:msg "epochLen"} old(_epochLen > MIN_EPOCH_LENGTH && _epochLen <= ONE_YEAR && epochLen != _epochLen) ==> nextEpochLen == _epochLen;
/// #if_succeeds {:msg "devsPerCapital"} _devsPerCapital > MIN_PARAM_VALUE && _devsPerCapital <= type(uint72).max ==> devsPerCapital == _devsPerCapital;
/// #if_succeeds {:msg "codePerDev"} _codePerDev > MIN_PARAM_VALUE && _codePerDev <= type(uint72).max ==> codePerDev == _codePerDev;
/// #if_succeeds {:msg "epsilonRate"} _epsilonRate > 0 && _epsilonRate < 17e18 ==> epsilonRate == _epsilonRate;
/// #if_succeeds {:msg "veOLASThreshold"} _veOLASThreshold > 0 && _veOLASThreshold <= type(uint96).max ==> nextVeOLASThreshold == _veOLASThreshold;
function changeTokenomicsParameters(
uint256 _devsPerCapital,
uint256 _codePerDev,
uint256 _epsilonRate,
uint256 _epochLen,
uint256 _veOLASThreshold
) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// devsPerCapital is the part of the IDF calculation and thus its change will be accounted for in the next epoch
if (uint72(_devsPerCapital) > MIN_PARAM_VALUE) {
devsPerCapital = uint72(_devsPerCapital);
} else {
// This is done in order not to pass incorrect parameters into the event
_devsPerCapital = devsPerCapital;
}
// devsPerCapital is the part of the IDF calculation and thus its change will be accounted for in the next epoch
if (uint72(_codePerDev) > MIN_PARAM_VALUE) {
codePerDev = uint72(_codePerDev);
} else {
// This is done in order not to pass incorrect parameters into the event
_codePerDev = codePerDev;
}
// Check the epsilonRate value for idf to fit in its size
// 2^64 - 1 < 18.5e18, idf is equal at most 1 + epsilonRate < 18e18, which fits in the variable size
// epsilonRate is the part of the IDF calculation and thus its change will be accounted for in the next epoch
if (_epsilonRate > 0 && _epsilonRate <= 17e18) {
epsilonRate = uint64(_epsilonRate);
} else {
_epsilonRate = epsilonRate;
}
// Check for the epochLen value to change
if (uint32(_epochLen) >= MIN_EPOCH_LENGTH && uint32(_epochLen) <= MAX_EPOCH_LENGTH) {
nextEpochLen = uint32(_epochLen);
} else {
_epochLen = epochLen;
}
// Adjust veOLAS threshold for the next epoch
if (uint96(_veOLASThreshold) > 0) {
nextVeOLASThreshold = uint96(_veOLASThreshold);
} else {
_veOLASThreshold = veOLASThreshold;
}
// Set the flag that tokenomics parameters are requested to be updated (1st bit is set to one)
tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x01;
emit TokenomicsParametersUpdateRequested(epochCounter + 1, _devsPerCapital, _codePerDev, _epsilonRate, _epochLen,
_veOLASThreshold);
}
/// @dev Sets incentive parameter fractions.
/// @param _rewardComponentFraction Fraction for component owner rewards funded by ETH donations.
/// @param _rewardAgentFraction Fraction for agent owner rewards funded by ETH donations.
/// @param _maxBondFraction Fraction for the maxBond that depends on the OLAS inflation.
/// @param _topUpComponentFraction Fraction for component owners OLAS top-up.
/// @param _topUpAgentFraction Fraction for agent owners OLAS top-up.
/// #if_succeeds {:msg "maxBond"} mapEpochTokenomics[epochCounter + 1].epochPoint.maxBondFraction == _maxBondFraction;
function changeIncentiveFractions(
uint256 _rewardComponentFraction,
uint256 _rewardAgentFraction,
uint256 _maxBondFraction,
uint256 _topUpComponentFraction,
uint256 _topUpAgentFraction,
uint256 _stakingFraction
) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check that the sum of fractions is 100%
if (_rewardComponentFraction + _rewardAgentFraction > 100) {
revert WrongAmount(_rewardComponentFraction + _rewardAgentFraction, 100);
}
// Same check for top-up fractions
uint256 sumTopUpFractions = _maxBondFraction + _topUpComponentFraction + _topUpAgentFraction +
_stakingFraction;
if (sumTopUpFractions > 100) {
revert WrongAmount(sumTopUpFractions, 100);
}
// All the adjustments will be accounted for in the next epoch
uint256 eCounter = epochCounter + 1;
TokenomicsPoint storage tp = mapEpochTokenomics[eCounter];
// 0 stands for components and 1 for agents
tp.unitPoints[0].rewardUnitFraction = uint8(_rewardComponentFraction);
tp.unitPoints[1].rewardUnitFraction = uint8(_rewardAgentFraction);
// Rewards are always distributed in full: the leftovers will be allocated to treasury
tp.epochPoint.rewardTreasuryFraction = uint8(100 - _rewardComponentFraction - _rewardAgentFraction);
tp.epochPoint.maxBondFraction = uint8(_maxBondFraction);
tp.unitPoints[0].topUpUnitFraction = uint8(_topUpComponentFraction);
tp.unitPoints[1].topUpUnitFraction = uint8(_topUpAgentFraction);
mapEpochStakingPoints[eCounter].stakingFraction = uint8(_stakingFraction);
// Set the flag that incentive fractions are requested to be updated (2nd bit is set to one)
tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x02;
emit IncentiveFractionsUpdateRequested(eCounter, _rewardComponentFraction, _rewardAgentFraction,
_maxBondFraction, _topUpComponentFraction, _topUpAgentFraction, _stakingFraction);
}
/// @dev Sets staking parameters by the DAO.
/// @param _maxStakingIncentive Max allowed staking incentive threshold.
/// @param _minStakingWeight Min staking weight threshold bound by 10_000.
function changeStakingParams(uint256 _maxStakingIncentive, uint256 _minStakingWeight) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for zero values
if (_maxStakingIncentive == 0 || _minStakingWeight == 0) {
revert ZeroValue();
}
// Check for overflows as per specs
if (_maxStakingIncentive > type(uint96).max) {
revert Overflow(_maxStakingIncentive, type(uint96).max);
}
if (_minStakingWeight > MAX_STAKING_WEIGHT) {
revert Overflow(_minStakingWeight, MAX_STAKING_WEIGHT);
}
// All the adjustments will be accounted for in the next epoch
uint256 eCounter = epochCounter + 1;
StakingPoint storage stakingPoint = mapEpochStakingPoints[eCounter];
stakingPoint.maxStakingIncentive = uint96(_maxStakingIncentive);
stakingPoint.minStakingWeight = uint16(_minStakingWeight);
// Set the flag that incentive fractions are requested to be updated (4th bit is set to one)
tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x08;
emit StakingParamsUpdateRequested(eCounter, _maxStakingIncentive, _minStakingWeight);
}
/// @dev Reserves OLAS amount from the effective bond to be minted during a bond program.
/// @notice Programs exceeding the limit of the effective bond are not allowed.
/// @param amount Requested amount for the bond program.
/// @return success True if effective bond threshold is not reached.
/// #if_succeeds {:msg "effectiveBond"} old(effectiveBond) > amount ==> effectiveBond == old(effectiveBond) - amount;
function reserveAmountForBondProgram(uint256 amount) external returns (bool success) {
// Check for the depository access
if (depository != msg.sender) {
revert ManagerOnly(msg.sender, depository);
}
// Effective bond must be bigger than the requested amount
uint256 eBond = effectiveBond;
if (eBond >= amount) {
// The effective bond value is adjusted with the amount that is reserved for bonding
// The unrealized part of the bonding amount will be returned when the bonding program is closed
eBond -= amount;
effectiveBond = uint96(eBond);
success = true;
emit EffectiveBondUpdated(epochCounter, eBond);
}
}
/// @dev Refunds unused bond program amount when the program is closed.
/// @param amount Amount to be refunded from the closed bond program.
/// #if_succeeds {:msg "effectiveBond"} old(effectiveBond + amount) <= type(uint96).max ==> effectiveBond == old(effectiveBond) + amount;
function refundFromBondProgram(uint256 amount) external {
// Check for the depository access
if (depository != msg.sender) {
revert ManagerOnly(msg.sender, depository);
}
uint256 eBond = effectiveBond + amount;
// This scenario is not realistically possible. It is only possible when closing the bonding program
// with the effectiveBond value close to uint96 max
if (eBond > type(uint96).max) {
revert Overflow(eBond, type(uint96).max);
}
effectiveBond = uint96(eBond);
emit EffectiveBondUpdated(epochCounter, eBond);
}
/// @dev Records amount returned back from staking to the inflation.
/// @param amount OLAS amount returned from staking.
function refundFromStaking(uint256 amount) external {
// Check for the dispenser access
if (dispenser != msg.sender) {
revert ManagerOnly(msg.sender, depository);
}
uint256 eCounter = epochCounter;
uint256 stakingIncentive = mapEpochStakingPoints[eCounter].stakingIncentive + amount;
// This scenario is not realistically possible, as the refund comes back from the allocated inflation.
if (stakingIncentive > type(uint96).max) {
revert Overflow(stakingIncentive, type(uint96).max);
}
mapEpochStakingPoints[eCounter].stakingIncentive = uint96(stakingIncentive);
emit StakingRefunded(eCounter, amount);
}
/// @dev Finalizes epoch incentives for a specified component / agent Id.
/// @param epochNum Epoch number to finalize incentives for.
/// @param unitType Unit type (component / agent).
/// @param unitId Unit Id.
function _finalizeIncentivesForUnitId(uint256 epochNum, uint256 unitType, uint256 unitId) internal {
// Gets the overall amount of unit rewards for the unit's last epoch
// The pendingRelativeReward can be zero if the rewardUnitFraction was zero in the first place
// Note that if the rewardUnitFraction is set to zero at the end of epoch, the whole pending reward will be zero
// reward = (pendingRelativeReward * rewardUnitFraction) / 100
uint256 totalIncentives = mapUnitIncentives[unitType][unitId].pendingRelativeReward;
if (totalIncentives > 0) {
totalIncentives *= mapEpochTokenomics[epochNum].unitPoints[unitType].rewardUnitFraction;
// Add to the final reward for the last epoch
totalIncentives = mapUnitIncentives[unitType][unitId].reward + totalIncentives / 100;
mapUnitIncentives[unitType][unitId].reward = uint96(totalIncentives);
// Setting pending reward to zero
mapUnitIncentives[unitType][unitId].pendingRelativeReward = 0;
}
// Add to the final top-up for the last epoch
totalIncentives = mapUnitIncentives[unitType][unitId].pendingRelativeTopUp;
// The pendingRelativeTopUp can be zero if the service owner did not stake enough veOLAS
// The topUpUnitFraction was checked before and if it were zero, pendingRelativeTopUp would be zero as well
if (totalIncentives > 0) {
// Summation of all the unit top-ups and total amount of top-ups per epoch
// topUp = (pendingRelativeTopUp * totalTopUpsOLAS * topUpUnitFraction) / (100 * sumUnitTopUpsOLAS)
totalIncentives *= mapEpochTokenomics[epochNum].epochPoint.totalTopUpsOLAS;
totalIncentives *= mapEpochTokenomics[epochNum].unitPoints[unitType].topUpUnitFraction;
uint256 sumUnitIncentives = uint256(mapEpochTokenomics[epochNum].unitPoints[unitType].sumUnitTopUpsOLAS) * 100;
totalIncentives = mapUnitIncentives[unitType][unitId].topUp + totalIncentives / sumUnitIncentives;
mapUnitIncentives[unitType][unitId].topUp = uint96(totalIncentives);
// Setting pending top-up to zero
mapUnitIncentives[unitType][unitId].pendingRelativeTopUp = 0;
}
}
/// @dev Records service donations into corresponding data structures.
/// @param donator Donator account address.
/// @param serviceIds Set of service Ids.
/// @param amounts Correspondent set of ETH amounts provided by services.
/// @param curEpoch Current epoch number.
function _trackServiceDonations(
address donator,
uint256[] memory serviceIds,
uint256[] memory amounts,
uint256 curEpoch
) internal {
// Component / agent registry addresses
address[] memory registries = new address[](2);
(registries[0], registries[1]) = (componentRegistry, agentRegistry);
// Check all the unit fractions and identify those that need accounting of incentives
bool[] memory incentiveFlags = new bool[](4);
incentiveFlags[0] = (mapEpochTokenomics[curEpoch].unitPoints[0].rewardUnitFraction > 0);
incentiveFlags[1] = (mapEpochTokenomics[curEpoch].unitPoints[1].rewardUnitFraction > 0);
incentiveFlags[2] = (mapEpochTokenomics[curEpoch].unitPoints[0].topUpUnitFraction > 0);
incentiveFlags[3] = (mapEpochTokenomics[curEpoch].unitPoints[1].topUpUnitFraction > 0);
// Get the number of services
uint256 numServices = serviceIds.length;
// Loop over service Ids to calculate their partial contributions
for (uint256 i = 0; i < numServices; ++i) {
// Check if the service owner or donator stakes enough OLAS for its components / agents to get a top-up
// If both component and agent owner top-up fractions are zero, there is no need to call external contract
// functions to check each service owner veOLAS balance
bool topUpEligible;
if (incentiveFlags[2] || incentiveFlags[3]) {
address serviceOwner = IToken(serviceRegistry).ownerOf(serviceIds[i]);
topUpEligible = (IVotingEscrow(ve).getVotes(serviceOwner) >= veOLASThreshold ||
IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold) ? true : false;
}
// Loop over component and agent Ids
for (uint256 unitType = 0; unitType < 2; ++unitType) {
// Get the number and set of units in the service
(uint256 numServiceUnits, uint32[] memory serviceUnitIds) = IServiceRegistry(serviceRegistry).
getUnitIdsOfService(IServiceRegistry.UnitType(unitType), serviceIds[i]);
// Service has to be deployed at least once to be able to receive donations,
// otherwise its components and agents are undefined
if (numServiceUnits == 0) {
revert ServiceNeverDeployed(serviceIds[i]);
}
// Record amounts data only if at least one incentive unit fraction is not zero
if (incentiveFlags[unitType] || incentiveFlags[unitType + 2]) {
// The amount has to be adjusted for the number of units in the service
uint96 amount = uint96(amounts[i] / numServiceUnits);
// Accumulate amounts for each unit Id
for (uint256 j = 0; j < numServiceUnits; ++j) {
// Get the last epoch number the incentives were accumulated for
uint256 lastEpoch = mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch;
// Check if there were no donations in previous epochs and set the current epoch
if (lastEpoch == 0) {
mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch = uint32(curEpoch);
} else if (lastEpoch < curEpoch) {
// Finalize unit rewards and top-ups if there were pending ones from the previous epoch
// Pending incentives are getting finalized during the next epoch the component / agent
// receives donations. If this is not the case before claiming incentives, the finalization
// happens in the accountOwnerIncentives() where the incentives are issued
_finalizeIncentivesForUnitId(lastEpoch, unitType, serviceUnitIds[j]);
// Change the last epoch number
mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch = uint32(curEpoch);
}
// Sum the relative amounts for the corresponding components / agents
if (incentiveFlags[unitType]) {
mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeReward += amount;
}
// If eligible, add relative top-up weights in the form of donation amounts.
// These weights will represent the fraction of top-ups for each component / agent relative
// to the overall amount of top-ups that must be allocated
if (topUpEligible && incentiveFlags[unitType + 2]) {
mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeTopUp += amount;
mapEpochTokenomics[curEpoch].unitPoints[unitType].sumUnitTopUpsOLAS += amount;
}
}
}
// Record new units and new unit owners
for (uint256 j = 0; j < numServiceUnits; ++j) {
// Check if the component / agent is used for the first time
if (!mapNewUnits[unitType][serviceUnitIds[j]]) {
mapNewUnits[unitType][serviceUnitIds[j]] = true;
mapEpochTokenomics[curEpoch].unitPoints[unitType].numNewUnits++;
// Check if the owner has introduced component / agent for the first time
// This is done together with the new unit check, otherwise it could be just a new unit owner
address unitOwner = IToken(registries[unitType]).ownerOf(serviceUnitIds[j]);
if (!mapNewOwners[unitOwner]) {
mapNewOwners[unitOwner] = true;
mapEpochTokenomics[curEpoch].epochPoint.numNewOwners++;
}
}
}
}
}
}
/// @dev Tracks the deposited ETH service donations during the current epoch.
/// @notice This function is only called by the treasury where the validity of arrays and values has been performed.
/// @notice Donating to services must not be followed by the checkpoint in the same block.
/// @param donator Donator account address.
/// @param serviceIds Set of service Ids.
/// @param amounts Correspondent set of ETH amounts provided by services.
/// @param donationETH Overall service donation amount in ETH.
/// #if_succeeds {:msg "totalDonationsETH can only increase"} old(mapEpochTokenomics[epochCounter].epochPoint.totalDonationsETH) + donationETH <= type(uint96).max
/// ==> mapEpochTokenomics[epochCounter].epochPoint.totalDonationsETH == old(mapEpochTokenomics[epochCounter].epochPoint.totalDonationsETH) + donationETH;
/// #if_succeeds {:msg "sumUnitTopUpsOLAS for components can only increase"} mapEpochTokenomics[epochCounter].unitPoints[0].sumUnitTopUpsOLAS >= old(mapEpochTokenomics[epochCounter].unitPoints[0].sumUnitTopUpsOLAS);
/// #if_succeeds {:msg "sumUnitTopUpsOLAS for agents can only increase"} mapEpochTokenomics[epochCounter].unitPoints[1].sumUnitTopUpsOLAS >= old(mapEpochTokenomics[epochCounter].unitPoints[1].sumUnitTopUpsOLAS);
/// #if_succeeds {:msg "numNewOwners can only increase"} mapEpochTokenomics[epochCounter].epochPoint.numNewOwners >= old(mapEpochTokenomics[epochCounter].epochPoint.numNewOwners);
function trackServiceDonations(
address donator,
uint256[] memory serviceIds,
uint256[] memory amounts,
uint256 donationETH
) external {
// Check for the treasury access
if (treasury != msg.sender) {
revert ManagerOnly(msg.sender, treasury);
}