ETH Price: $2,414.88 (-4.48%)
Gas: 0.25 Gwei

Transaction Decoder

Block:
22212681 at Apr-06-2025 09:54:47 PM +UTC
Transaction Fee:
0.000688267945819125 ETH $1.66
Gas Used:
142,575 Gas / 4.827409755 Gwei

Emitted Events:

267 Treasury.Withdraw( token=0xEeeeeEee...eeeeeEEeE, to=[Sender] 0xee9b087c43c1a267cfdc63f14cec451c8b305997, tokenAmount=170000000000000000 )
268 OLAS.Transfer( from=0x0000000000000000000000000000000000000000, to=[Sender] 0xee9b087c43c1a267cfdc63f14cec451c8b305997, amount=6255915977741059281606 )
269 Treasury.Withdraw( token=OLAS, to=[Sender] 0xee9b087c43c1a267cfdc63f14cec451c8b305997, tokenAmount=6255915977741059281606 )
270 Dispenser.IncentivesClaimed( owner=[Sender] 0xee9b087c43c1a267cfdc63f14cec451c8b305997, reward=170000000000000000, topUp=6255915977741059281606, unitTypes=[1], unitIds=[38] )

Account State Difference:

  Address   Before After State Difference Code
0x0001A500...c28E45CB0
(Titan Builder)
7.514735862013521123 Eth7.515092299513521123 Eth0.0003564375
0xa0DA5344...628B30f82 39.816617807575900339 Eth39.646617807575900339 Eth0.17
0xc096362f...3381ce300
0xEe9b087C...c8b305997
0.775981556604034191 Eth
Nonce: 336
0.945293288658215066 Eth
Nonce: 337
0.169311732054180875

Execution Trace

Dispenser.claimOwnerIncentives( unitTypes=[1], unitIds=[38] ) => ( reward=170000000000000000, topUp=6255915977741059281606 )
  • Treasury.CALL( )
  • TokenomicsProxy.2e070f54( )
    • Tokenomics.accountOwnerIncentives( account=0xEe9b087C43c1A267cFDC63f14ceC451c8b305997, unitTypes=[1], unitIds=[38] ) => ( reward=170000000000000000, topUp=6255915977741059281606 )
      • ComponentRegistry.STATICCALL( )
      • AgentRegistry.STATICCALL( )
      • AgentRegistry.ownerOf( id=38 ) => ( owner=0xEe9b087C43c1A267cFDC63f14ceC451c8b305997 )
      • OLAS.balanceOf( 0xEe9b087C43c1A267cFDC63f14ceC451c8b305997 ) => ( 0 )
      • Treasury.withdrawToAccount( account=0xEe9b087C43c1A267cFDC63f14ceC451c8b305997, accountRewards=170000000000000000, accountTopUps=6255915977741059281606 ) => ( success=True )
        • ETH 0.17 0xee9b087c43c1a267cfdc63f14cec451c8b305997.CALL( )
        • OLAS.mint( account=0xEe9b087C43c1A267cFDC63f14ceC451c8b305997, amount=6255915977741059281606 )
        • OLAS.balanceOf( 0xEe9b087C43c1A267cFDC63f14ceC451c8b305997 ) => ( 6255915977741059281606 )
          claimOwnerIncentives[Dispenser (ln:724)]
          File 1 of 7: Dispenser
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.25;
          // Deposit Processor interface
          interface IDepositProcessor {
              /// @dev Sends a single message to the L2 side via a corresponding bridge.
              /// @param target Staking target addresses.
              /// @param stakingIncentive Corresponding staking incentive.
              /// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
              /// @param transferAmount Actual OLAS amount to be transferred.
              function sendMessage(address target, uint256 stakingIncentive, bytes memory bridgePayload,
                  uint256 transferAmount) external payable;
              /// @dev Sends a batch message to the L2 side via a corresponding bridge.
              /// @param targets Set of staking target addresses.
              /// @param stakingIncentives Corresponding set of staking incentives.
              /// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
              /// @param transferAmount Actual total OLAS amount across all the targets to be transferred.
              function sendMessageBatch(address[] memory targets, uint256[] memory stakingIncentives, bytes memory bridgePayload,
                  uint256 transferAmount) external payable;
              /// @dev Sends a single message to the non-EVM chain.
              /// @param target Staking target addresses.
              /// @param stakingIncentive Corresponding staking incentive.
              /// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
              /// @param transferAmount Actual OLAS amount to be transferred.
              function sendMessageNonEVM(bytes32 target, uint256 stakingIncentive, bytes memory bridgePayload,
                  uint256 transferAmount) external payable;
              /// @dev Sends a batch message to the non-EVM chain.
              /// @param targets Set of staking target addresses.
              /// @param stakingIncentives Corresponding set of staking incentives.
              /// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
              /// @param transferAmount Actual total OLAS amount across all the targets to be transferred.
              function sendMessageBatchNonEVM(bytes32[] memory targets, uint256[] memory stakingIncentives,
                  bytes memory bridgePayload, uint256 transferAmount) external payable;
              /// @dev Gets the maximum number of token decimals able to be transferred across the bridge.
              /// @return Number of supported decimals.
              function getBridgingDecimals() external pure returns (uint256);
              /// @dev Updated the batch hash of a failed message, if applicable.
              /// @param batchHash Unique batch hash for each message transfer.
              function updateHashMaintenance(bytes32 batchHash) external;
          }
          // ERC20 token interface
          interface IToken {
              /// @dev Gets the amount of tokens owned by a specified account.
              /// @param account Account address.
              /// @return Amount of tokens owned.
              function balanceOf(address account) external view returns (uint256);
              /// @dev Transfers the token amount.
              /// @param to Address to transfer to.
              /// @param amount The amount to transfer.
              /// @return True if the function execution is successful.
              function transfer(address to, uint256 amount) external returns (bool);
          }
          // Tokenomics interface
          interface ITokenomics {
              // 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 <= 100%
                  // Amount of OLAS (in percentage of inflation) intended to fund bonding incentives during the epoch
                  uint8 maxBondFraction;
              }
              // 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;
              }
              /// @dev Gets component / agent owner incentives and clears the balances.
              /// @notice `account` must be the owner of components / agents Ids, otherwise the function will revert.
              /// @notice If not all `unitIds` belonging to `account` were provided, they will be untouched and keep accumulating.
              /// @notice Component and agent Ids must be provided in the ascending order and must not repeat.
              /// @param account Account address.
              /// @param unitTypes Set of unit types (component / agent).
              /// @param unitIds Set of corresponding unit Ids where account is the owner.
              /// @return reward Reward amount.
              /// @return topUp Top-up amount.
              function accountOwnerIncentives(address account, uint256[] memory unitTypes, uint256[] memory unitIds) external
                  returns (uint256 reward, uint256 topUp);
              /// @dev Gets tokenomics epoch counter.
              /// @return Epoch counter.
              function epochCounter() external view returns (uint32);
              /// @dev Gets tokenomics epoch length.
              /// @return Epoch length.
              function epochLen() external view returns (uint32);
              /// @dev Gets epoch end time.
              /// @param epoch Epoch number.
              /// @return endTime Epoch end time.
              function getEpochEndTime(uint256 epoch) external view returns (uint256 endTime);
              /// @dev Gets tokenomics epoch service staking point.
              /// @param eCounter Epoch number.
              /// @return Staking point.
              function mapEpochStakingPoints(uint256 eCounter) external view returns (StakingPoint memory);
              /// @dev Records amount returned back from staking to the inflation.
              /// @param amount OLAS amount returned from staking.
              function refundFromStaking(uint256 amount) external;
          }
          // Treasury interface
          interface ITreasury {
              /// @dev Withdraws ETH and / or OLAS amounts to the requested account address.
              /// @notice Only dispenser contract can call this function.
              /// @notice Reentrancy guard is on a dispenser side.
              /// @notice Zero account address is not possible, since the dispenser contract interacts with msg.sender.
              /// @param account Account address.
              /// @param accountRewards Amount of account rewards.
              /// @param accountTopUps Amount of account top-ups.
              /// @return success True if the function execution is successful.
              function withdrawToAccount(address account, uint256 accountRewards, uint256 accountTopUps) external
                  returns (bool success);
              /// @dev Returns the paused state.
              /// @return Paused state.
              function paused() external returns (uint8);
          }
          // Vote Weighting nterface
          interface IVoteWeighting {
              // Nominee struct
              struct Nominee {
                  bytes32 account;
                  uint256 chainId;
              }
              /// @dev Gets the nominee Id by its hash.
              /// @param nomineeHash Nominee hash derived from its account address and chainId.
              /// @return Nominee Id.
              function mapNomineeIds(bytes32 nomineeHash) external returns (uint256);
              /// @dev Checkpoint to fill data for both a specific nominee and common for all nominees.
              /// @param account Address of the nominee.
              /// @param chainId Chain Id.
              function checkpointNominee(bytes32 account, uint256 chainId) external;
              /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 and the sum of weights.
              ///         (e.g. 1.0 == 1e18). Inflation which will be received by it is
              ///         inflation_rate * relativeWeight / 1e18.
              /// @param account Address of the nominee in bytes32 form.
              /// @param chainId Chain Id.
              /// @param time Relative weight at the specified timestamp in the past or present.
              /// @return Value of relative weight normalized to 1e18.
              /// @return Sum of nominee weights.
              function nomineeRelativeWeight(bytes32 account, uint256 chainId, uint256 time) external view returns (uint256, 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 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 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 Caught reentrancy violation.
          error ReentrancyGuard();
          /// @dev The contract is paused.
          error Paused();
          /// @dev Incentives claim has failed.
          /// @param account Account address.
          /// @param reward Reward amount.
          /// @param topUp Top-up amount.
          error ClaimIncentivesFailed(address account, uint256 reward, uint256 topUp);
          /// @dev Only the deposit processor is able to call the function.
          /// @param sender Actual sender address.
          /// @param depositProcessor Required deposit processor.
          error DepositProcessorOnly(address sender, address depositProcessor);
          /// @dev Chain Id is incorrect.
          /// @param chainId Chain Id.
          error WrongChainId(uint256 chainId);
          /// @dev Account address is incorrect.
          /// @param account Account address.
          error WrongAccount(bytes32 account);
          /// @title Dispenser - Smart contract for distributing incentives
          /// @author Aleksandr Kuperman - <[email protected]>
          /// @author Andrey Lebedev - <[email protected]>
          /// @author Mariapia Moscatiello - <[email protected]>
          contract Dispenser {
              enum Pause {
                  Unpaused,
                  DevIncentivesPaused,
                  StakingIncentivesPaused,
                  AllPaused
              }
              event OwnerUpdated(address indexed owner);
              event TokenomicsUpdated(address indexed tokenomics);
              event TreasuryUpdated(address indexed treasury);
              event VoteWeightingUpdated(address indexed voteWeighting);
              event StakingParamsUpdated(uint256 maxNumClaimingEpochs, uint256 maxNumStakingTargets);
              event IncentivesClaimed(address indexed owner, uint256 reward, uint256 topUp, uint256[] unitTypes, uint256[] unitIds);
              event StakingIncentivesClaimed(address indexed account, uint256 chainId, bytes32 stakingTarget,
                  uint256 stakingIncentive, uint256 transferAmount, uint256 returnAmount);
              event StakingIncentivesBatchClaimed(address indexed account, uint256[] chainIds, bytes32[][] stakingTargets,
                  uint256[][] stakingIncentives, uint256 totalStakingIncentive, uint256 totalTransferAmount,
                  uint256 totalReturnAmount);
              event Retained(address indexed account, uint256 returnAmount);
              event SetDepositProcessorChainIds(address[] depositProcessors, uint256[] chainIds);
              event WithheldAmountSynced(uint256 chainId, uint256 amount, uint256 updatedWithheldAmount, bytes32 indexed batchHash);
              event PauseDispenser(Pause pauseState);
              event AddNomineeHash(bytes32 indexed nomineeHash);
              event RemoveNomineeHash(bytes32 indexed nomineeHash);
              // Maximum chain Id as per EVM specs
              uint256 public constant MAX_EVM_CHAIN_ID = type(uint64).max / 2 - 36;
              uint256 public immutable defaultMinStakingWeight;
              uint256 public immutable defaultMaxStakingIncentive;
              // OLAS token address
              address public immutable olas;
              // Retainer address in bytes32 form
              bytes32 public immutable retainer;
              // Retainer hash of a Nominee struct composed of retainer address with block.chainid
              bytes32 public immutable retainerHash;
              // Max number of epochs to claim staking incentives for
              uint256 public maxNumClaimingEpochs;
              // Max number of targets for a specific chain to claim staking incentives for
              uint256 public maxNumStakingTargets;
              // Owner address
              address public owner;
              // Reentrancy lock
              uint8 internal _locked;
              // Pause state
              Pause public paused;
              // Tokenomics contract address
              address public tokenomics;
              // Treasury contract address
              address public treasury;
              // Vote Weighting contract address
              address public voteWeighting;
              // Mapping for hash(Nominee struct) service staking pair => last claimed epochs
              mapping(bytes32 => uint256) public mapLastClaimedStakingEpochs;
              // Mapping for hash(Nominee struct) service staking pair => epoch number when the staking contract is removed
              mapping(bytes32 => uint256) public mapRemovedNomineeEpochs;
              // Mapping for L2 chain Id => dedicated deposit processors
              mapping(uint256 => address) public mapChainIdDepositProcessors;
              // Mapping for L2 chain Id => withheld OLAS amounts
              mapping(uint256 => uint256) public mapChainIdWithheldAmounts;
              // Mapping for epoch number => refunded all the staking inflation due to zero total voting power
              mapping(uint256 => bool) public mapZeroWeightEpochRefunded;
              /// @dev Dispenser constructor.
              /// @param _olas OLAS token address.
              /// @param _tokenomics Tokenomics address.
              /// @param _treasury Treasury address.
              /// @param _voteWeighting Vote Weighting address.
              constructor(
                  address _olas,
                  address _tokenomics,
                  address _treasury,
                  address _voteWeighting,
                  bytes32 _retainer,
                  uint256 _maxNumClaimingEpochs,
                  uint256 _maxNumStakingTargets,
                  uint256 _defaultMinStakingWeight,
                  uint256 _defaultMaxStakingIncentive
              ) {
                  owner = msg.sender;
                  _locked = 1;
                  // Staking incentives must be paused at the time of deployment because staking parameters are not live yet
                  paused = Pause.StakingIncentivesPaused;
                  // Check for at least one zero contract address
                  if (_olas == address(0) || _tokenomics == address(0) || _treasury == address(0) ||
                      _voteWeighting == address(0) || _retainer == 0) {
                      revert ZeroAddress();
                  }
                  // Check for zero value staking parameters
                  if (_maxNumClaimingEpochs == 0 || _maxNumStakingTargets == 0 || _defaultMinStakingWeight == 0 ||
                      _defaultMaxStakingIncentive == 0) {
                      revert ZeroValue();
                  }
                  // Check for maximum values
                  if (_defaultMinStakingWeight > type(uint16).max) {
                      revert Overflow(_defaultMinStakingWeight, type(uint16).max);
                  }
                  if (_defaultMaxStakingIncentive > type(uint96).max) {
                      revert Overflow(_defaultMaxStakingIncentive, type(uint96).max);
                  }
                  olas = _olas;
                  tokenomics = _tokenomics;
                  treasury = _treasury;
                  voteWeighting = _voteWeighting;
                  retainer = _retainer;
                  retainerHash = keccak256(abi.encode(IVoteWeighting.Nominee(retainer, block.chainid)));
                  maxNumClaimingEpochs = _maxNumClaimingEpochs;
                  maxNumStakingTargets = _maxNumStakingTargets;
                  defaultMinStakingWeight = _defaultMinStakingWeight;
                  defaultMaxStakingIncentive = _defaultMaxStakingIncentive;
              }
              /// @dev Checkpoints specified staking target (nominee in Vote Weighting) and gets claimed epoch counters.
              /// @param nomineeHash Hash of a Nominee(stakingTarget, chainId).
              /// @param numClaimedEpochs Specified number of claimed epochs.
              /// @return firstClaimedEpoch First claimed epoch number.
              /// @return lastClaimedEpoch Last claimed epoch number (not included in claiming).
              function _checkpointNomineeAndGetClaimedEpochCounters(
                  bytes32 nomineeHash,
                  uint256 numClaimedEpochs
              ) internal view returns (uint256 firstClaimedEpoch, uint256 lastClaimedEpoch) {
                  // Get the current epoch number
                  uint256 eCounter = ITokenomics(tokenomics).epochCounter();
                  // Get the first claimed epoch, which is equal to the last claiming one
                  firstClaimedEpoch = mapLastClaimedStakingEpochs[nomineeHash];
                  // This must never happen as the nominee gets enabled when added to Vote Weighting
                  // This is only possible if the dispenser has been unset in Vote Weighting for some time
                  // In that case the check is correct and those nominees must not be considered
                  if (firstClaimedEpoch == 0) {
                      revert ZeroValue();
                  }
                  // Must not claim in the ongoing epoch
                  if (firstClaimedEpoch == eCounter) {
                      // Epoch counter is never equal to zero
                      revert Overflow(firstClaimedEpoch, eCounter - 1);
                  }
                  // Get epoch number when the nominee was removed
                  uint256 epochRemoved = mapRemovedNomineeEpochs[nomineeHash];
                  // If the nominee is not removed, its value in the map is always zero
                  // The staking contract nominee cannot be removed in the zero-th epoch by default
                  if (epochRemoved > 0 && firstClaimedEpoch >= epochRemoved) {
                      revert Overflow(firstClaimedEpoch, epochRemoved - 1);
                  }
                  // Get a number of epochs to claim for based on the maximum number of epochs claimed
                  lastClaimedEpoch = firstClaimedEpoch + numClaimedEpochs;
                  // Limit last claimed epoch by the number following the nominee removal epoch
                  // The condition for is lastClaimedEpoch strictly > because the lastClaimedEpoch is not included in claiming
                  if (epochRemoved > 0 && lastClaimedEpoch > epochRemoved) {
                      lastClaimedEpoch = epochRemoved;
                  }
                  // Also limit by the current counter, if the nominee was removed in the current epoch
                  if (lastClaimedEpoch > eCounter) {
                      lastClaimedEpoch = eCounter;
                  }
              }
              /// @dev Distributes staking incentives to a corresponding staking target.
              /// @param chainId Chain Id.
              /// @param stakingTarget Staking target corresponding to the chain Id.
              /// @param stakingIncentive Corresponding staking incentive.
              /// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
              /// @param transferAmount Actual OLAS amount to be transferred.
              function _distributeStakingIncentives(
                  uint256 chainId,
                  bytes32 stakingTarget,
                  uint256 stakingIncentive,
                  bytes memory bridgePayload,
                  uint256 transferAmount
              ) internal {
                  // Get the deposit processor contract address
                  address depositProcessor = mapChainIdDepositProcessors[chainId];
                  // Transfer corresponding OLAS amounts to the deposit processor
                  if (transferAmount > 0) {
                      IToken(olas).transfer(depositProcessor, transferAmount);
                  }
                  if (chainId <= MAX_EVM_CHAIN_ID) {
                      address stakingTargetEVM = address(uint160(uint256(stakingTarget)));
                      IDepositProcessor(depositProcessor).sendMessage{value:msg.value}(stakingTargetEVM, stakingIncentive,
                          bridgePayload, transferAmount);
                  } else {
                      // Send to non-EVM
                      IDepositProcessor(depositProcessor).sendMessageNonEVM{value:msg.value}(stakingTarget,
                          stakingIncentive, bridgePayload, transferAmount);
                  }
              }
              /// @dev Distributes staking incentives to corresponding staking targets.
              /// @param chainIds Set of chain Ids.
              /// @param stakingTargets Set of staking target addresses corresponding to each chain Id.
              /// @param stakingIncentives Corresponding set of staking incentives.
              /// @param bridgePayloads Bridge payloads (if) necessary for a specific bridge relayer depending on chain Id.
              /// @param transferAmounts Set of actual total OLAS amounts across all the targets to be transferred.
              /// @param valueAmounts Set of value amounts required to provide to some of the bridges.
              function _distributeStakingIncentivesBatch(
                  uint256[] memory chainIds,
                  bytes32[][] memory stakingTargets,
                  uint256[][] memory stakingIncentives,
                  bytes[] memory bridgePayloads,
                  uint256[] memory transferAmounts,
                  uint256[] memory valueAmounts
              ) internal {
                  // Traverse all staking targets
                  for (uint256 i = 0; i < chainIds.length; ++i) {
                      // Get the deposit processor contract address
                      address depositProcessor = mapChainIdDepositProcessors[chainIds[i]];
                      // Transfer corresponding OLAS amounts to deposit processors
                      if (transferAmounts[i] > 0) {
                          IToken(olas).transfer(depositProcessor, transferAmounts[i]);
                      }
                      // Find zero staking incentives
                      uint256 numActualTargets;
                      bool[] memory positions = new bool[](stakingTargets[i].length);
                      for (uint256 j = 0; j < stakingTargets[i].length; ++j) {
                          if (stakingIncentives[i][j] > 0) {
                              positions[j] = true;
                              ++numActualTargets;
                          }
                      }
                      // Skip if there are no actual staking targets
                      if (numActualTargets == 0) {
                          continue;
                      }
                      // Allocate updated arrays accounting only for nonzero staking incentives
                      bytes32[] memory updatedStakingTargets = new bytes32[](numActualTargets);
                      uint256[] memory updatedStakingAmounts = new uint256[](numActualTargets);
                      uint256 numPos;
                      for (uint256 j = 0; j < stakingTargets[i].length; ++j) {
                          if (positions[j]) {
                              updatedStakingTargets[numPos] = stakingTargets[i][j];
                              updatedStakingAmounts[numPos] = stakingIncentives[i][j];
                              ++numPos;
                          }
                      }
                      // Address conversion depending on chain Ids
                      if (chainIds[i] <= MAX_EVM_CHAIN_ID) {
                          // Convert to EVM addresses
                          address[] memory stakingTargetsEVM = new address[](updatedStakingTargets.length);
                          for (uint256 j = 0; j < updatedStakingTargets.length; ++j) {
                              stakingTargetsEVM[j] = address(uint160(uint256(updatedStakingTargets[j])));
                          }
                          // Send to EVM chains
                          IDepositProcessor(depositProcessor).sendMessageBatch{value:valueAmounts[i]}(stakingTargetsEVM,
                              updatedStakingAmounts, bridgePayloads[i], transferAmounts[i]);
                      } else {
                          // Send to non-EVM chains
                          IDepositProcessor(depositProcessor).sendMessageBatchNonEVM{value:valueAmounts[i]}(updatedStakingTargets,
                              updatedStakingAmounts, bridgePayloads[i], transferAmounts[i]);
                      }
                  }
              }
              /// @dev Checks strict ascending order of chain Ids and staking targets to ensure the absence of duplicates.
              /// @param chainIds Set of chain Ids.
              /// @notice The function is not view deliberately such that all the reverts are executed correctly.
              /// @param stakingTargets Set of staking target addresses corresponding to each chain Id.
              /// @param bridgePayloads Set of bridge payloads (if) necessary for a specific bridge relayer depending on chain Id.
              /// @param valueAmounts Set of value amounts required to provide to some of the bridges.
              function _checkOrderAndValues(
                  uint256[] memory chainIds,
                  bytes32[][] memory stakingTargets,
                  bytes[] memory bridgePayloads,
                  uint256[] memory valueAmounts
              ) internal {
                  // Check array sizes
                  if (chainIds.length != stakingTargets.length) {
                      revert WrongArrayLength(chainIds.length, stakingTargets.length);
                  }
                  if (chainIds.length != bridgePayloads.length) {
                      revert WrongArrayLength(chainIds.length, bridgePayloads.length);
                  }
                  if (chainIds.length != valueAmounts.length) {
                      revert WrongArrayLength(chainIds.length, valueAmounts.length);
                  }
                  uint256 lastChainId;
                  uint256 totalValueAmount;
                  // Traverse all chains
                  for (uint256 i = 0; i < chainIds.length; ++i) {
                      // Check that chain Ids are strictly in ascending non-repeatable order
                      // Also protects from the initial chainId == 0
                      if (lastChainId >= chainIds[i]) {
                          revert WrongChainId(chainIds[i]);
                      }
                      lastChainId = chainIds[i];
                      // Staking targets must not be an empty array
                      if (stakingTargets[i].length == 0) {
                          revert ZeroValue();
                      }
                      // Add to the total value amount
                      totalValueAmount += valueAmounts[i];
                      // Check for the maximum number of staking targets
                      uint256 localMaxNumStakingTargets = maxNumStakingTargets;
                      if (stakingTargets[i].length > localMaxNumStakingTargets) {
                          revert Overflow(stakingTargets[i].length, localMaxNumStakingTargets);
                      }
                      bytes32 lastTarget;
                      // Traverse all staking targets
                      for (uint256 j = 0; j < stakingTargets[i].length; ++j) {
                          // Enforce ascending non-repeatable order of targets
                          // Also protects from the initial stakingTargets[i][j] == 0
                          if (uint256(lastTarget) >= uint256(stakingTargets[i][j])) {
                              revert WrongAccount(stakingTargets[i][j]);
                          }
                          lastTarget = stakingTargets[i][j];
                      }
                  }
                  // Check if the total transferred amount corresponds to the sum of value amounts
                  if (msg.value != totalValueAmount) {
                      revert WrongAmount(msg.value, totalValueAmount);
                  }
              }
              /// @dev Calculates staking incentives for sets of staking targets corresponding to a set of chain Ids.
              /// @param numClaimedEpochs Specified number of claimed epochs.
              /// @param chainIds Set of chain Ids.
              /// @param stakingTargets Set of staking target addresses corresponding to each chain Id.
              /// @return totalAmounts Total calculated amounts: staking, transfer, return.
              /// @return stakingIncentives Sets of staking incentives.
              /// @return transferAmounts Set of transfer amounts.
              function _calculateStakingIncentivesBatch(
                  uint256 numClaimedEpochs,
                  uint256[] memory chainIds,
                  bytes32[][] memory stakingTargets
              ) internal returns (
                  uint256[] memory totalAmounts,
                  uint256[][] memory stakingIncentives,
                  uint256[] memory transferAmounts
              ) {
                  // Total staking incentives
                  // 0: Staking incentive across all the targets to send as a deposit
                  // 1: Actual OLAS transfer amount across all the targets
                  // 2: Staking incentive to return back to staking inflation
                  totalAmounts = new uint256[](3);
                  // Allocate the array of staking and transfer amounts
                  stakingIncentives = new uint256[][](chainIds.length);
                  transferAmounts = new uint256[](chainIds.length);
                  // Traverse all chains
                  for (uint256 i = 0; i < chainIds.length; ++i) {
                      // Get deposit processor bridging decimals corresponding to a chain Id
                      address depositProcessor = mapChainIdDepositProcessors[chainIds[i]];
                      uint256 bridgingDecimals = IDepositProcessor(depositProcessor).getBridgingDecimals();
                      stakingIncentives[i] = new uint256[](stakingTargets[i].length);
                      // Traverse all staking targets
                      for (uint256 j = 0; j < stakingTargets[i].length; ++j) {
                          // Check that staking target is not a retainer address
                          if (stakingTargets[i][j] == retainer) {
                              revert WrongAccount(retainer);
                          }
                          // Get the staking incentive to send as a deposit with, and the amount to return back to staking inflation
                          (uint256 stakingIncentive, uint256 returnAmount, uint256 lastClaimedEpoch, bytes32 nomineeHash) =
                              calculateStakingIncentives(numClaimedEpochs, chainIds[i], stakingTargets[i][j], bridgingDecimals);
                          // Write last claimed epoch counter to start claiming from the next time
                          mapLastClaimedStakingEpochs[nomineeHash] = lastClaimedEpoch;
                          stakingIncentives[i][j] = stakingIncentive;
                          transferAmounts[i] += stakingIncentive;
                          totalAmounts[0] += stakingIncentive;
                          totalAmounts[2] += returnAmount;
                      }
                      // Account for possible withheld OLAS amounts
                      if (transferAmounts[i] > 0) {
                          uint256 withheldAmount = mapChainIdWithheldAmounts[chainIds[i]];
                          if (withheldAmount > 0) {
                              if (withheldAmount >= transferAmounts[i]) {
                                  withheldAmount -= transferAmounts[i];
                                  transferAmounts[i] = 0;
                              } else {
                                  transferAmounts[i] -= withheldAmount;
                                  withheldAmount = 0;
                              }
                              mapChainIdWithheldAmounts[chainIds[i]] = withheldAmount;
                          }
                      }
                      // Add to the total transfer amount
                      totalAmounts[1] += transferAmounts[i];
                  }
              }
              /// @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 _tokenomics Tokenomics address.
              /// @param _treasury Treasury address.
              /// @param _voteWeighting Vote Weighting address.
              function changeManagers(address _tokenomics, address _treasury, address _voteWeighting) external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Change Tokenomics contract address
                  if (_tokenomics != address(0)) {
                      tokenomics = _tokenomics;
                      emit TokenomicsUpdated(_tokenomics);
                  }
                  // Change Treasury contract address
                  if (_treasury != address(0)) {
                      treasury = _treasury;
                      emit TreasuryUpdated(_treasury);
                  }
                  // Change Vote Weighting contract address
                  if (_voteWeighting != address(0)) {
                      voteWeighting = _voteWeighting;
                      emit VoteWeightingUpdated(_voteWeighting);
                  }
              }
              /// @dev Changes staking params by the DAO.
              /// @param _maxNumClaimingEpochs Maximum number of epochs to claim staking incentives for.
              /// @param _maxNumStakingTargets Maximum number of staking targets available to claim for on a single chain Id.
              function changeStakingParams(uint256 _maxNumClaimingEpochs, uint256 _maxNumStakingTargets) external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check if values are zero
                  if (_maxNumClaimingEpochs == 0 || _maxNumStakingTargets == 0) {
                      revert ZeroValue();
                  }
                  maxNumClaimingEpochs = _maxNumClaimingEpochs;
                  maxNumStakingTargets = _maxNumStakingTargets;
                  emit StakingParamsUpdated(_maxNumClaimingEpochs, _maxNumStakingTargets);
              }
              /// @dev Sets deposit processor contracts addresses and L2 chain Ids.
              /// @notice It is the contract owner responsibility to set correct L1 deposit processor contracts
              ///         and corresponding supported L2 chain Ids.
              /// @param depositProcessors Set of deposit processor contract addresses on L1.
              /// @param chainIds Set of corresponding L2 chain Ids.
              function setDepositProcessorChainIds(address[] memory depositProcessors, uint256[] memory chainIds) external {
                  // Check for the ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for array length correctness
                  if (depositProcessors.length == 0 || depositProcessors.length != chainIds.length) {
                      revert WrongArrayLength(depositProcessors.length, chainIds.length);
                  }
                  // Link L1 and L2 bridge mediators, set L2 chain Ids
                  for (uint256 i = 0; i < chainIds.length; ++i) {
                      // Check supported chain Ids on L2
                      if (chainIds[i] == 0) {
                          revert ZeroValue();
                      }
                      // Note: depositProcessors[i] might be zero if there is a need to stop processing a specific L2 chain Id
                      mapChainIdDepositProcessors[chainIds[i]] = depositProcessors[i];
                  }
                  emit SetDepositProcessorChainIds(depositProcessors, chainIds);
              }
              /// @dev Records nominee starting epoch number.
              /// @param nomineeHash Nominee hash.
              function addNominee(bytes32 nomineeHash) external {
                  // Check for the contract ownership
                  if (msg.sender != voteWeighting) {
                      revert ManagerOnly(msg.sender, voteWeighting);
                  }
                  // Check for the paused state
                  Pause currentPause = paused;
                  if (currentPause == Pause.StakingIncentivesPaused || currentPause == Pause.AllPaused ||
                      ITreasury(treasury).paused() == 2) {
                      revert Paused();
                  }
                  mapLastClaimedStakingEpochs[nomineeHash] = ITokenomics(tokenomics).epochCounter();
                  emit AddNomineeHash(nomineeHash);
              }
              /// @dev Records nominee removal epoch number.
              /// @notice The staking contract nominee cannot be removed starting from one week before the end of epoch.
              ///         Since the epoch end time is unknown and the nominee removal is applied in the following week,
              ///         it is prohibited to remove nominee one week before the foreseen epoch end to correctly reflect
              ///         the removal epoch number.
              ///         If the staking contract nominee must not get incentives in the ongoing ending epoch as well,
              ///         the DAO is advised to use the removeInstance() function in the corresponding StakingFactory contract.
              /// @param nomineeHash Nominee hash.
              function removeNominee(bytes32 nomineeHash) external {
                  // Check for the contract ownership
                  if (msg.sender != voteWeighting) {
                      revert ManagerOnly(msg.sender, voteWeighting);
                  }
                  // Check for the retainer hash
                  if (retainerHash == nomineeHash) {
                      revert WrongAccount(retainer);
                  }
                  // Get the epoch counter
                  uint256 eCounter = ITokenomics(tokenomics).epochCounter();
                  // Get the previous epoch end time
                  // Epoch counter is never equal to zero
                  uint256 endTime = ITokenomics(tokenomics).getEpochEndTime(eCounter - 1);
                  // Get the epoch length
                  uint256 epochLen = ITokenomics(tokenomics).epochLen();
                  // Check that there is more than one week before the end of the ongoing epoch
                  // Note that epochLen cannot be smaller than one week as per specified limits
                  uint256 maxAllowedTime = endTime + epochLen - 1 weeks;
                  if (block.timestamp >= maxAllowedTime) {
                      revert Overflow(block.timestamp, maxAllowedTime);
                  }
                  // Set the removed nominee epoch number
                  mapRemovedNomineeEpochs[nomineeHash] = eCounter;
                  emit RemoveNomineeHash(nomineeHash);
              }
              /// @dev Claims incentives for the owner of components / agents.
              /// @notice `msg.sender` must be the owner of components / agents they are passing, otherwise the function will revert.
              /// @notice If not all `unitIds` belonging to `msg.sender` were provided, they will be untouched and keep accumulating.
              /// @param unitTypes Set of unit types (component / agent).
              /// @param unitIds Set of corresponding unit Ids where account is the owner.
              /// @return reward Reward amount in ETH.
              /// @return topUp Top-up amount in OLAS.
              function claimOwnerIncentives(
                  uint256[] memory unitTypes,
                  uint256[] memory unitIds
              ) external returns (uint256 reward, uint256 topUp) {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  // Check for the paused state
                  Pause currentPause = paused;
                  if (currentPause == Pause.DevIncentivesPaused || currentPause == Pause.AllPaused ||
                      ITreasury(treasury).paused() == 2) {
                      revert Paused();
                  }
                  // Calculate incentives
                  (reward, topUp) = ITokenomics(tokenomics).accountOwnerIncentives(msg.sender, unitTypes, unitIds);
                  bool success;
                  // Request treasury to transfer funds to msg.sender if reward > 0 or topUp > 0
                  if ((reward + topUp) > 0) {
                      // Get the current OLAS balance
                      uint256 olasBalance;
                      if (topUp > 0) {
                          olasBalance = IToken(olas).balanceOf(msg.sender);
                      }
                      success = ITreasury(treasury).withdrawToAccount(msg.sender, reward, topUp);
                      // Check the balance after the OLAS mint, if applicable
                      if (topUp > 0){
                          olasBalance = IToken(olas).balanceOf(msg.sender) - olasBalance;
                          if (olasBalance != topUp) {
                              revert WrongAmount(olasBalance, topUp);
                          }
                      }
                  }
                  // Check if the claim is successful and has at least one non-zero incentive.
                  if (!success) {
                      revert ClaimIncentivesFailed(msg.sender, reward, topUp);
                  }
                  emit IncentivesClaimed(msg.sender, reward, topUp, unitTypes, unitIds);
                  _locked = 1;
              }
              /// @dev Calculates staking incentives for a specific staking target.
              /// @notice Call this function via staticcall in order not to write in the nominee checkpoint map.
              /// @param numClaimedEpochs Specified number of claimed epochs.
              /// @param chainId Chain Id.
              /// @param stakingTarget Staking target corresponding to the chain Id.
              /// @param bridgingDecimals Number of supported token decimals able to be transferred across the bridge.
              /// @return totalStakingIncentive Total staking incentive across all the claimed epochs.
              /// @return totalReturnAmount Total return amount across all the claimed epochs.
              /// @return lastClaimedEpoch Last claimed epoch number (not included in claiming).
              /// @return nomineeHash Hash of a Nominee(stakingTarget, chainId).
              function calculateStakingIncentives(
                  uint256 numClaimedEpochs,
                  uint256 chainId,
                  bytes32 stakingTarget,
                  uint256 bridgingDecimals
              ) public returns (
                  uint256 totalStakingIncentive,
                  uint256 totalReturnAmount,
                  uint256 lastClaimedEpoch,
                  bytes32 nomineeHash
              ) {
                  // Check for the correct chain Id
                  if (chainId == 0) {
                      revert ZeroValue();
                  }
                  // Check for the zero address
                  if (stakingTarget == 0) {
                      revert ZeroAddress();
                  }
                  // Check that staking target is not a retainer address
                  if (stakingTarget == retainer) {
                      revert WrongAccount(retainer);
                  }
                  // Get the staking target nominee hash
                  nomineeHash = keccak256(abi.encode(IVoteWeighting.Nominee(stakingTarget, chainId)));
                  uint256 firstClaimedEpoch;
                  (firstClaimedEpoch, lastClaimedEpoch) =
                      _checkpointNomineeAndGetClaimedEpochCounters(nomineeHash, numClaimedEpochs);
                  // Checkpoint staking target nominee in the Vote Weighting contract
                  IVoteWeighting(voteWeighting).checkpointNominee(stakingTarget, chainId);
                  // Traverse all the claimed epochs
                  for (uint256 j = firstClaimedEpoch; j < lastClaimedEpoch; ++j) {
                      // Check if epoch had zero total vote weights and all its staking incentives have been already refunded
                      // back to tokenomics inflation
                      if (mapZeroWeightEpochRefunded[j]) {
                          continue;
                      }
                      // Get service staking info
                      ITokenomics.StakingPoint memory stakingPoint =
                          ITokenomics(tokenomics).mapEpochStakingPoints(j);
                      // Check for staking parameters to all make sense
                      if (stakingPoint.stakingFraction > 0) {
                          // Check for unset values
                          if (stakingPoint.minStakingWeight == 0 && stakingPoint.maxStakingIncentive == 0) {
                              stakingPoint.minStakingWeight = uint16(defaultMinStakingWeight);
                              stakingPoint.maxStakingIncentive = uint96(defaultMaxStakingIncentive);
                          }
                      } else {
                          // No staking incentives in this epoch
                          continue;
                      }
                      uint256 endTime = ITokenomics(tokenomics).getEpochEndTime(j);
                      // Get the staking weight for each epoch and the total weight
                      // Epoch endTime is used to get the weights info, since otherwise there is a risk of having votes
                      // accounted for from the next epoch
                      // totalWeightSum is the overall veOLAS power (bias) across all the voting nominees
                      (uint256 stakingWeight, uint256 totalWeightSum) =
                          IVoteWeighting(voteWeighting).nomineeRelativeWeight(stakingTarget, chainId, endTime);
                      // Check if the totalWeightSum is zero, then all staking incentives must be returned back to tokenomics
                      if (totalWeightSum == 0) {
                          mapZeroWeightEpochRefunded[j] = true;
                          totalReturnAmount += stakingPoint.stakingIncentive;
                          continue;
                      }
                      uint256 stakingIncentive;
                      uint256 returnAmount;
                      // Adjust the inflation by the maximum amount of veOLAS voted for service staking contracts
                      // If veOLAS power is lower, it reflects the maximum amount of OLAS allocated for staking
                      // such that all the inflation is not distributed for a minimal veOLAS power
                      uint256 availableStakingAmount = stakingPoint.stakingIncentive;
                      uint256 stakingDiff;
                      if (availableStakingAmount > totalWeightSum) {
                          stakingDiff = availableStakingAmount - totalWeightSum;
                          availableStakingAmount = totalWeightSum;
                      }
                      // Compare the staking weight
                      // 100% = 1e18, in order to compare with minStakingWeight we need to bring it from the range of 0 .. 10_000
                      if (stakingWeight < uint256(stakingPoint.minStakingWeight) * 1e14) {
                          // If vote weighting staking weight is lower than the defined threshold - return the staking incentive
                          returnAmount = ((stakingDiff + availableStakingAmount) * stakingWeight) / 1e18;
                      } else {
                          // Otherwise, allocate staking incentive to corresponding contracts
                          stakingIncentive = (availableStakingAmount * stakingWeight) / 1e18;
                          // Calculate initial return amount, if stakingDiff > 0
                          returnAmount = (stakingDiff * stakingWeight) / 1e18;
                          // availableStakingAmount is not used anymore and can serve as a local maxStakingIncentive
                          availableStakingAmount = stakingPoint.maxStakingIncentive;
                          if (stakingIncentive > availableStakingAmount) {
                              // Adjust the return amount
                              returnAmount += stakingIncentive - availableStakingAmount;
                              // Adjust the staking incentive
                              stakingIncentive = availableStakingAmount;
                          }
                          // Normalize staking incentive if there is a bridge decimals limiting condition
                          // Note: only OLAS decimals must be considered
                          if (bridgingDecimals < 18) {
                              uint256 normalizedStakingAmount = stakingIncentive / (10 ** (18 - bridgingDecimals));
                              normalizedStakingAmount *= 10 ** (18 - bridgingDecimals);
                              // Update return amounts
                              // stakingIncentive is always bigger or equal than normalizedStakingAmount
                              returnAmount += stakingIncentive - normalizedStakingAmount;
                              // Downsize staking incentive to a specified number of bridging decimals
                              stakingIncentive = normalizedStakingAmount;
                          }
                          // Adjust total staking incentive amount
                          totalStakingIncentive += stakingIncentive;
                      }
                      // Adjust total return amount
                      totalReturnAmount += returnAmount;
                  }
              }
              /// @dev Claims staking incentives for a specific staking target.
              /// @param numClaimedEpochs Specified number of claimed epochs.
              /// @param chainId Chain Id.
              /// @param stakingTarget Staking target corresponding to the chain Id.
              /// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
              function claimStakingIncentives(
                  uint256 numClaimedEpochs,
                  uint256 chainId,
                  bytes32 stakingTarget,
                  bytes memory bridgePayload
              ) external payable {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  // Check for zero chain Id
                  if (chainId == 0) {
                      revert ZeroValue();
                  }
                  // Check for zero target address
                  if (stakingTarget == 0) {
                      revert ZeroAddress();
                  }
                  // Check the number of claimed epochs
                  uint256 localMaxNumClaimingEpochs = maxNumClaimingEpochs;
                  if (numClaimedEpochs > localMaxNumClaimingEpochs) {
                      revert Overflow(numClaimedEpochs, localMaxNumClaimingEpochs);
                  }
                  // Check for the paused state
                  Pause currentPause = paused;
                  if (currentPause == Pause.StakingIncentivesPaused || currentPause == Pause.AllPaused ||
                      ITreasury(treasury).paused() == 2) {
                      revert Paused();
                  }
                  // Get deposit processor bridging decimals corresponding to a chain Id
                  address depositProcessor = mapChainIdDepositProcessors[chainId];
                  uint256 bridgingDecimals = IDepositProcessor(depositProcessor).getBridgingDecimals();
                  // Get the staking incentive to send as a deposit with, and the amount to return back to staking inflation
                  (uint256 stakingIncentive, uint256 returnAmount, uint256 lastClaimedEpoch, bytes32 nomineeHash) =
                      calculateStakingIncentives(numClaimedEpochs, chainId, stakingTarget, bridgingDecimals);
                  // Write last claimed epoch counter to start claiming from the next time
                  mapLastClaimedStakingEpochs[nomineeHash] = lastClaimedEpoch;
                  // Refund returned amount back to tokenomics inflation
                  if (returnAmount > 0) {
                      ITokenomics(tokenomics).refundFromStaking(returnAmount);
                  }
                  uint256 transferAmount;
                  // Check if staking incentive is deposited
                  if (stakingIncentive > 0) {
                      transferAmount = stakingIncentive;
                      // Account for possible withheld OLAS amounts
                      // Note: in case of normalized staking incentives with bridging decimals, this is correctly managed
                      // as normalized amounts are returned from another side
                      uint256 withheldAmount = mapChainIdWithheldAmounts[chainId];
                      if (withheldAmount > 0) {
                          // If withheld amount is enough to cover all the staking incentives, the transfer of OLAS is not needed
                          if (withheldAmount >= transferAmount) {
                              withheldAmount -= transferAmount;
                              transferAmount = 0;
                          } else {
                              // Otherwise, reduce the transfer of tokens for the OLAS withheld amount
                              transferAmount -= withheldAmount;
                              withheldAmount = 0;
                          }
                          mapChainIdWithheldAmounts[chainId] = withheldAmount;
                      }
                      // Check if minting is needed as the actual OLAS transfer is required
                      if (transferAmount > 0) {
                          uint256 olasBalance = IToken(olas).balanceOf(address(this));
                          // Mint tokens to self in order to distribute to the staking deposit processor
                          ITreasury(treasury).withdrawToAccount(address(this), 0, transferAmount);
                          // Check the balance after the mint
                          olasBalance = IToken(olas).balanceOf(address(this)) - olasBalance;
                          if (olasBalance != transferAmount) {
                              revert WrongAmount(olasBalance, transferAmount);
                          }
                      }
                      // Dispense to a service staking target
                      _distributeStakingIncentives(chainId, stakingTarget, stakingIncentive, bridgePayload, transferAmount);
                  }
                  emit StakingIncentivesClaimed(msg.sender, chainId, stakingTarget, stakingIncentive, transferAmount, returnAmount);
                  _locked = 1;
              }
              /// @dev Claims staking incentives for sets staking targets corresponding to a set of chain Ids.
              /// @notice Mind the gas spending depending on the max number of numChains * numTargetsPerChain * numEpochs to claim.
              ///         Also note that in order to avoid duplicates, there is a requirement for a strict ascending order
              ///         of chain Ids and stakingTargets.
              /// @param numClaimedEpochs Specified number of claimed epochs.
              /// @param chainIds Set of chain Ids.
              /// @param stakingTargets Set of staking target addresses corresponding to each chain Id.
              /// @param bridgePayloads Set of bridge payloads (if) necessary for a specific bridge relayer depending on chain Id.
              /// @param valueAmounts Set of value amounts required to provide to some of the bridges.
              function claimStakingIncentivesBatch(
                  uint256 numClaimedEpochs,
                  uint256[] memory chainIds,
                  bytes32[][] memory stakingTargets,
                  bytes[] memory bridgePayloads,
                  uint256[] memory valueAmounts
              ) external payable {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  _checkOrderAndValues(chainIds, stakingTargets, bridgePayloads, valueAmounts);
                  // Check the number of claimed epochs
                  uint256 localMaxNumClaimingEpochs = maxNumClaimingEpochs;
                  if (numClaimedEpochs > localMaxNumClaimingEpochs) {
                      revert Overflow(numClaimedEpochs, localMaxNumClaimingEpochs);
                  }
                  Pause currentPause = paused;
                  if (currentPause == Pause.StakingIncentivesPaused || currentPause == Pause.AllPaused ||
                      ITreasury(treasury).paused() == 2) {
                      revert Paused();
                  }
                  // Total staking incentives
                  // 0: Staking incentive across all the targets to send as a deposit
                  // 1: Actual OLAS transfer amount across all the targets
                  // 2: Staking incentive to return back to staking inflation
                  uint256[] memory totalAmounts;
                  // Arrays of staking and transfer amounts
                  uint256[][] memory stakingIncentives;
                  uint256[] memory transferAmounts;
                  (totalAmounts, stakingIncentives, transferAmounts) = _calculateStakingIncentivesBatch(numClaimedEpochs, chainIds,
                      stakingTargets);
                  // Refund returned amount back to tokenomics inflation
                  if (totalAmounts[2] > 0) {
                      ITokenomics(tokenomics).refundFromStaking(totalAmounts[2]);
                  }
                  // Check if staking incentive is deposited
                  if (totalAmounts[0] > 0) {
                      // Check if minting is needed as the actual OLAS transfer is required
                      if (totalAmounts[1] > 0) {
                          uint256 olasBalance = IToken(olas).balanceOf(address(this));
                          // Mint tokens to self in order to distribute to staking deposit processors
                          ITreasury(treasury).withdrawToAccount(address(this), 0, totalAmounts[1]);
                          // Check the balance after the mint
                          olasBalance = IToken(olas).balanceOf(address(this)) - olasBalance;
                          if (olasBalance != totalAmounts[1]) {
                              revert WrongAmount(olasBalance, totalAmounts[1]);
                          }
                      }
                      // Dispense all the service staking targets, if the total staking incentive is not equal to zero
                      _distributeStakingIncentivesBatch(chainIds, stakingTargets, stakingIncentives, bridgePayloads, transferAmounts,
                          valueAmounts);
                  }
                  emit StakingIncentivesBatchClaimed(msg.sender, chainIds, stakingTargets, stakingIncentives, totalAmounts[0],
                      totalAmounts[1], totalAmounts[2]);
                  _locked = 1;
              }
              /// @dev Retains staking incentives according to the retainer address to return it back to the staking inflation.
              function retain() external {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  // Get first and last claimed epochs
                  (uint256 firstClaimedEpoch, uint256 lastClaimedEpoch) =
                      _checkpointNomineeAndGetClaimedEpochCounters(retainerHash, maxNumClaimingEpochs);
                  // Checkpoint staking target nominee in the Vote Weighting contract
                  IVoteWeighting(voteWeighting).checkpointNominee(retainer, block.chainid);
                  // Write last claimed epoch counter to start retaining from the next time
                  mapLastClaimedStakingEpochs[retainerHash] = lastClaimedEpoch;
                  uint256 totalReturnAmount;
                  // Go over epochs and retain funds to return back to the tokenomics
                  for (uint256 j = firstClaimedEpoch; j < lastClaimedEpoch; ++j) {
                      // Get service staking info
                      ITokenomics.StakingPoint memory stakingPoint = ITokenomics(tokenomics).mapEpochStakingPoints(j);
                      // Get epoch end time
                      uint256 endTime = ITokenomics(tokenomics).getEpochEndTime(j);
                      // Get the staking weight for each epoch
                      (uint256 stakingWeight, ) = IVoteWeighting(voteWeighting).nomineeRelativeWeight(retainer,
                          block.chainid, endTime);
                      totalReturnAmount += stakingPoint.stakingIncentive * stakingWeight;
                  }
                  totalReturnAmount /= 1e18;
                  if (totalReturnAmount > 0) {
                      ITokenomics(tokenomics).refundFromStaking(totalReturnAmount);
                  }
                  emit Retained(msg.sender, totalReturnAmount);
                  _locked = 1;
              }
              /// @dev Syncs the withheld token amount according to the data received from L2.
              /// @notice Only a corresponding chain Id deposit processor is able to communicate the withheld amount data.
              ///         Note that by design only a normalized withheld amount is delivered from L2.
              /// @param chainId L2 chain Id the withheld amount data is communicated from.
              /// @param amount Withheld OLAS token amount.
              /// @param batchHash Unique batch hash for each message transfer.
              function syncWithheldAmount(uint256 chainId, uint256 amount, bytes32 batchHash) external {
                  address depositProcessor = mapChainIdDepositProcessors[chainId];
                  // Check L1 deposit processor address
                  if (msg.sender != depositProcessor) {
                      revert DepositProcessorOnly(msg.sender, depositProcessor);
                  }
                  // The overall amount is bound by the OLAS projected maximum amount for years to come
                  uint256 withheldAmount = mapChainIdWithheldAmounts[chainId] + amount;
                  if (withheldAmount > type(uint96).max) {
                      revert Overflow(withheldAmount, type(uint96).max);
                  }
                  // Update the withheld amount
                  mapChainIdWithheldAmounts[chainId] = withheldAmount;
                  emit WithheldAmountSynced(chainId, amount, withheldAmount, batchHash);
              }
              /// @dev Syncs the withheld amount manually by the DAO in order to restore the data that was not delivered from L2.
              /// @notice The parameters here must correspond to the exact data failed to be delivered (amount, batch).
              ///         The possible bridge failure scenario that requires to act via the DAO vote includes:
              ///         - Message from L2 to L1 fails: need to call this function.
              /// @param chainId L2 chain Id.
              /// @param amount Withheld amount that was not delivered from L2.
              /// @param batchHash Unique batch hash for each message transfer.
              function syncWithheldAmountMaintenance(uint256 chainId, uint256 amount, bytes32 batchHash) external {
                  // Check the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check zero value chain Id and amount
                  if (chainId == 0 || amount == 0 || batchHash == 0) {
                      revert ZeroValue();
                  }
                  // The sync must never happen for the L1 chain Id itself, as dispenser exists strictly on L1-s
                  if (chainId == block.chainid) {
                      revert WrongChainId(chainId);
                  }
                  // Note: all the amounts coming from events of undelivered messages are already normalized
                  uint256 withheldAmount = mapChainIdWithheldAmounts[chainId] + amount;
                  // The overall amount is bound by the OLAS projected maximum amount for years to come
                  if (withheldAmount > type(uint96).max) {
                      revert Overflow(withheldAmount, type(uint96).max);
                  }
                  // Add to the withheld amount
                  mapChainIdWithheldAmounts[chainId] = withheldAmount;
                  // Get deposit processor address corresponding to the specified chain Id
                  address depositProcessor = mapChainIdDepositProcessors[chainId];
                  // Update the batch hash on deposit processor side
                  IDepositProcessor(depositProcessor).updateHashMaintenance(batchHash);
                  emit WithheldAmountSynced(chainId, amount, withheldAmount, batchHash);
              }
              /// @dev Sets the pause state.
              /// @param pauseState Pause state.
              function setPauseState(Pause pauseState) external {
                  // Check the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  paused = pauseState;
                  emit PauseDispenser(pauseState);
              }
          }
          

          File 2 of 7: Treasury
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev Errors.
          interface IErrorsTokenomics {
              /// @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 Non-zero value when it has to be zero.
              error NonZeroValue();
              /// @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 Token is disabled or not whitelisted.
              /// @param tokenAddress Address of a token.
              error UnauthorizedToken(address tokenAddress);
              /// @dev Provided token address is incorrect.
              /// @param provided Provided token address.
              /// @param expected Expected token address.
              error WrongTokenAddress(address provided, address expected);
              /// @dev Bond is not redeemable (does not exist or not matured).
              /// @param bondId Bond Id.
              error BondNotRedeemable(uint256 bondId);
              /// @dev The product is expired.
              /// @param tokenAddress Address of a token.
              /// @param productId Product Id.
              /// @param deadline The program expiry time.
              /// @param curTime Current timestamp.
              error ProductExpired(address tokenAddress, uint256 productId, uint256 deadline, uint256 curTime);
              /// @dev The product is already closed.
              /// @param productId Product Id.
              error ProductClosed(uint256 productId);
              /// @dev The product supply is low for the requested payout.
              /// @param tokenAddress Address of a token.
              /// @param productId Product Id.
              /// @param requested Requested payout.
              /// @param actual Actual supply left.
              error ProductSupplyLow(address tokenAddress, uint256 productId, uint256 requested, uint256 actual);
              /// @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 Insufficient token allowance.
              /// @param provided Provided amount.
              /// @param expected Minimum expected amount.
              error InsufficientAllowance(uint256 provided, uint256 expected);
              /// @dev Failure of a transfer.
              /// @param token Address of a token.
              /// @param from Address `from`.
              /// @param to Address `to`.
              /// @param amount Token amount.
              error TransferFailed(address token, address from, address to, uint256 amount);
              /// @dev Incentives claim has failed.
              /// @param account Account address.
              /// @param reward Reward amount.
              /// @param topUp Top-up amount.
              error ClaimIncentivesFailed(address account, uint256 reward, uint256 topUp);
              /// @dev Caught reentrancy violation.
              error ReentrancyGuard();
              /// @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);
              /// @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 The contract is paused.
              error Paused();
              /// @dev Caught an operation that is not supposed to happen in the same block.
              error SameBlockNumberViolation();
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          interface IOLAS {
              /// @dev Mints OLA tokens.
              /// @param account Account address.
              /// @param amount OLA token amount.
              function mint(address account, uint256 amount) external;
              /// @dev Provides OLA token time launch.
              /// @return Time launch.
              function timeLaunch() external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev Required interface for the service registry.
          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);
              /// @dev Gets the value of slashed funds from the service registry.
              /// @return amount Drained amount.
              function slashedFunds() external view returns (uint256 amount);
              /// @dev Drains slashed funds.
              /// @return amount Drained amount.
              function drain() external returns (uint256 amount);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev Generic token interface for IERC20 and IERC721 tokens.
          interface IToken {
              /// @dev Gets the amount of tokens owned by a specified account.
              /// @param account Account address.
              /// @return Amount of tokens owned.
              function balanceOf(address account) external view returns (uint256);
              /// @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);
              /// @dev Transfers the token amount.
              /// @param to Address to transfer to.
              /// @param amount The amount to transfer.
              /// @return True if the function execution is successful.
              function transfer(address to, uint256 amount) external returns (bool);
              /// @dev Gets remaining number of tokens that the `spender` can transfer on behalf of `owner`.
              /// @param owner Token owner.
              /// @param spender Account address that is able to transfer tokens on behalf of the owner.
              /// @return Token amount allowed to be transferred.
              function allowance(address owner, address spender) external view returns (uint256);
              /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
              /// @param spender Account address that will be able to transfer tokens on behalf of the caller.
              /// @param amount Token amount.
              /// @return True if the function execution is successful.
              function approve(address spender, uint256 amount) external returns (bool);
              /// @dev Transfers the token amount that was previously approved up until the maximum allowance.
              /// @param from Account address to transfer from.
              /// @param to Account address to transfer to.
              /// @param amount Amount to transfer to.
              /// @return True if the function execution is successful.
              function transferFrom(address from, address to, uint256 amount) external returns (bool);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev Interface for tokenomics management.
          interface ITokenomics {
              /// @dev Gets effective bond (bond left).
              /// @return Effective bond.
              function effectiveBond() external pure returns (uint256);
              /// @dev Record global data to the checkpoint
              function checkpoint() external returns (bool);
              /// @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.
              /// @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.
              function trackServiceDonations(
                  address donator,
                  uint256[] memory serviceIds,
                  uint256[] memory amounts,
                  uint256 donationETH
              ) external;
              /// @dev Reserves OLAS amount from the effective bond to be minted during a bond program.
              /// @notice Programs exceeding the limit in the epoch are not allowed.
              /// @param amount Requested amount for the bond program.
              /// @return True if effective bond threshold is not reached.
              function reserveAmountForBondProgram(uint256 amount) external returns(bool);
              /// @dev Refunds unused bond program amount.
              /// @param amount Amount to be refunded from the bond program.
              function refundFromBondProgram(uint256 amount) external;
              /// @dev Gets component / agent owner incentives and clears the balances.
              /// @param account Account address.
              /// @param unitTypes Set of unit types (component / agent).
              /// @param unitIds Set of corresponding unit Ids where account is the owner.
              /// @return reward Reward amount.
              /// @return topUp Top-up amount.
              function accountOwnerIncentives(address account, uint256[] memory unitTypes, uint256[] memory unitIds) external
                  returns (uint256 reward, uint256 topUp);
              /// @dev Gets inverse discount factor with the multiple of 1e18 of the last epoch.
              /// @return idf Discount factor with the multiple of 1e18.
              function getLastIDF() external view returns (uint256 idf);
              /// @dev Gets the service registry contract address
              /// @return Service registry contract address;
              function serviceRegistry() external view returns (address);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          import "./interfaces/IErrorsTokenomics.sol";
          import "./interfaces/IOLAS.sol";
          import "./interfaces/IToken.sol";
          import "./interfaces/IServiceRegistry.sol";
          import "./interfaces/ITokenomics.sol";
          /*
          * 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.
          *
          * In conclusion, this contract is only safe to use until 2106.
          */
          /// @title Treasury - Smart contract for managing OLAS Treasury
          /// @author AL
          /// @author Aleksandr Kuperman - <[email protected]>
          /// Invariant does not support a failing call() function while transferring ETH when using the CEI pattern:
          /// revert TransferFailed(address(0), address(this), to, tokenAmount);
          /// invariant {:msg "broken conservation law"} address(this).balance == ETHFromServices + ETHOwned;
          contract Treasury is IErrorsTokenomics {
              event OwnerUpdated(address indexed owner);
              event TokenomicsUpdated(address indexed tokenomics);
              event DepositoryUpdated(address indexed depository);
              event DispenserUpdated(address indexed dispenser);
              event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
              event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
              event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);
              event EnableToken(address indexed token);
              event DisableToken(address indexed token);
              event ReceiveETH(address indexed sender, uint256 amount);
              event UpdateTreasuryBalances(uint256 ETHOwned, uint256 ETHFromServices);
              event PauseTreasury();
              event UnpauseTreasury();
              event MinAcceptedETHUpdated(uint256 amount);
              // A well-known representation of an ETH as address
              address public constant ETH_TOKEN_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
              // Owner address
              address public owner;
              // ETH received from services
              // 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 public ETHFromServices;
              // OLAS token address
              address public olas;
              // ETH owned by treasury
              // 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 public ETHOwned;
              // Tkenomics contract address
              address public tokenomics;
              // Minimum accepted donation value
              uint96 public minAcceptedETH = 0.065 ether;
              // Depository contract address
              address public depository;
              // Contract pausing
              uint8 public paused = 1;
              // Reentrancy lock
              uint8 internal _locked;
              
              // Dispenser contract address
              address public dispenser;
              // Token address => token reserves
              mapping(address => uint256) public mapTokenReserves;
              // Token address => enabled / disabled status
              mapping(address => bool) public mapEnabledTokens;
              /// @dev Treasury constructor.
              /// @param _olas OLAS token address.
              /// @param _tokenomics Tokenomics address.
              /// @param _depository Depository address.
              /// @param _dispenser Dispenser address.
              constructor(address _olas, address _tokenomics, address _depository, address _dispenser) payable {
                  owner = msg.sender;
                  _locked = 1;
                  // Check for at least one zero contract address
                  if (_olas == address(0) || _tokenomics == address(0) || _depository == address(0) || _dispenser == address(0)) {
                      revert ZeroAddress();
                  }
                  olas = _olas;
                  tokenomics = _tokenomics;
                  depository = _depository;
                  dispenser = _dispenser;
                  // Assign an initial contract address ETH balance
                  // If msg.value is passed in the constructor, it is already accounted for in the address balance
                  // This way the balance also accounts for possible transfers before the contract was created
                  ETHOwned = uint96(address(this).balance);
              }
              /// @dev Receives ETH.
              /// #if_succeeds {:msg "we do not touch the balance of developers" } old(ETHFromServices) == ETHFromServices;
              /// #if_succeeds {:msg "conservation law"} old(ETHOwned) + msg.value + old(ETHFromServices) <= type(uint96).max && ETHOwned == old(ETHOwned) + msg.value
              /// ==> address(this).balance == ETHFromServices + ETHOwned;
              /// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
              receive() external payable {
                  if (msg.value < minAcceptedETH) {
                      revert LowerThan(msg.value, minAcceptedETH);
                  }
                  uint256 amount = ETHOwned;
                  amount += msg.value;
                  // Check for the overflow values, specifically when fuzzing, since practically these amounts are not realistic
                  if (amount + ETHFromServices > type(uint96).max) {
                      revert Overflow(amount, type(uint96).max);
                  }
                  ETHOwned = uint96(amount);
                  emit ReceiveETH(msg.sender, msg.value);
              }
              /// @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 _tokenomics Tokenomics address.
              /// @param _depository Depository address.
              /// @param _dispenser Dispenser address.
              function changeManagers(address _tokenomics, address _depository, address _dispenser) external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Change Tokenomics contract address
                  if (_tokenomics != address(0)) {
                      tokenomics = _tokenomics;
                      emit TokenomicsUpdated(_tokenomics);
                  }
                  // 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 minimum accepted ETH amount by the Treasury.
              /// @param _minAcceptedETH New minimum accepted ETH amount.
              /// #if_succeeds {:msg "Min accepted ETH"} minAcceptedETH > 0 && minAcceptedETH <= type(uint96).max;
              function changeMinAcceptedETH(uint256 _minAcceptedETH) external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for the zero value
                  if (_minAcceptedETH == 0) {
                      revert ZeroValue();
                  }
                  // Check for the overflow value
                  if (_minAcceptedETH > type(uint96).max) {
                      revert Overflow(_minAcceptedETH, type(uint96).max);
                  }
                  minAcceptedETH = uint96(_minAcceptedETH);
                  emit MinAcceptedETHUpdated(_minAcceptedETH);
              }
              /// @dev Allows the depository to deposit LP tokens for OLAS.
              /// @notice Only depository contract can call this function.
              /// @param account Account address making a deposit of LP tokens for OLAS.
              /// @param tokenAmount Token amount to get OLAS for.
              /// @param token Token address.
              /// @param olasMintAmount Amount of OLAS token issued.
              /// #if_succeeds {:msg "we do not touch the total eth balance"} old(address(this).balance) == address(this).balance;
              /// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
              /// #if_succeeds {:msg "OLAS balances"} IToken(olas).balanceOf(msg.sender) == old(IToken(olas).balanceOf(msg.sender)) + olasMintAmount;
              /// #if_succeeds {:msg "OLAS supply"} IToken(olas).totalSupply() == old(IToken(olas).totalSupply()) + olasMintAmount;
              function depositTokenForOLAS(address account, uint256 tokenAmount, address token, uint256 olasMintAmount) external {
                  // Check for the depository access
                  if (depository != msg.sender) {
                      revert ManagerOnly(msg.sender, depository);
                  }
                  // Check if the token is authorized by the registry
                  if (!mapEnabledTokens[token]) {
                      revert UnauthorizedToken(token);
                  }
                  // Increase the amount of LP token reserves
                  uint256 reserves = mapTokenReserves[token] + tokenAmount;
                  mapTokenReserves[token] = reserves;
                  // Uniswap allowance implementation does not revert with the accurate message, need to check before the transfer is engaged
                  if (IToken(token).allowance(account, address(this)) < tokenAmount) {
                      revert InsufficientAllowance(IToken(token).allowance((account), address(this)), tokenAmount);
                  }
                  // Transfer tokens from account to treasury and add to the token treasury reserves
                  // We assume that authorized LP tokens in the protocol are safe as they are enabled via the governance
                  // UniswapV2ERC20 realization has a standard transferFrom() function that returns a boolean value
                  bool success = IToken(token).transferFrom(account, address(this), tokenAmount);
                  if (!success) {
                      revert TransferFailed(token, account, address(this), tokenAmount);
                  }
                  // Mint specified number of OLAS tokens corresponding to tokens bonding deposit
                  // The olasMintAmount is guaranteed by the product supply limit, which is limited by the effectiveBond
                  IOLAS(olas).mint(msg.sender, olasMintAmount);
                  emit DepositTokenFromAccount(account, token, tokenAmount, olasMintAmount);
              }
              /// @dev Deposits service donations in ETH.
              /// @notice Each provided service Id must be deployed at least once, otherwise its components and agents are undefined.
              /// @notice If a specific service is terminated with agent Ids being updated, incentives will be issued to its old
              ///         configuration component / agent owners until the service is re-deployed when new agent Ids are accounted for.
              /// @param serviceIds Set of service Ids.
              /// @param amounts Set of corresponding amounts deposited on behalf of each service Id.
              /// #if_succeeds {:msg "we do not touch the owners balance"} old(ETHOwned) == ETHOwned;
              /// #if_succeeds {:msg "updated ETHFromServices"} old(ETHFromServices) + msg.value + old(ETHOwned) <= type(uint96).max && ETHFromServices == old(ETHFromServices) + msg.value
              /// ==> address(this).balance == ETHFromServices + ETHOwned;
              /// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
              function depositServiceDonationsETH(uint256[] memory serviceIds, uint256[] memory amounts) external payable {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  // Check that the amount donated has at least a practical minimal value
                  if (msg.value < minAcceptedETH) {
                      revert LowerThan(msg.value, minAcceptedETH);
                  }
                  // Check for the same length of arrays
                  uint256 numServices = serviceIds.length;
                  if (amounts.length != numServices) {
                      revert WrongArrayLength(numServices, amounts.length);
                  }
                  uint256 totalAmount;
                  for (uint256 i = 0; i < numServices; ++i) {
                      if (amounts[i] == 0) {
                          revert ZeroValue();
                      }
                      totalAmount += amounts[i];
                  }
                  // Check if the total transferred amount corresponds to the sum of amounts from services
                  if (msg.value != totalAmount) {
                      revert WrongAmount(msg.value, totalAmount);
                  }
                  // Accumulate received donation from services
                  uint256 donationETH = ETHFromServices + msg.value;
                  // Check for the overflow values, specifically when fuzzing, since realistically these amounts are assumed to be not possible
                  if (donationETH + ETHOwned > type(uint96).max) {
                      revert Overflow(donationETH, type(uint96).max);
                  }
                  ETHFromServices = uint96(donationETH);
                  emit DonateToServicesETH(msg.sender, serviceIds, amounts, msg.value);
                  // Track service donations on the Tokenomics side
                  ITokenomics(tokenomics).trackServiceDonations(msg.sender, serviceIds, amounts, msg.value);
                  _locked = 1;
              }
              /// @dev Allows owner to transfer tokens from treasury reserves to a specified address.
              /// @param to Address to transfer funds to.
              /// @param tokenAmount Token amount to get reserves from.
              /// @param token Token or ETH address.
              /// @return success True if the transfer is successful.
              /// #if_succeeds {:msg "we do not touch the balance of developers"} old(ETHFromServices) == ETHFromServices;
              /// #if_succeeds {:msg "updated ETHOwned"} token == ETH_TOKEN_ADDRESS ==> ETHOwned == old(ETHOwned) - tokenAmount;
              /// #if_succeeds {:msg "ETH balance"} token == ETH_TOKEN_ADDRESS ==> address(this).balance == old(address(this).balance) - tokenAmount;
              /// #if_succeeds {:msg "updated token reserves"} token != ETH_TOKEN_ADDRESS ==> mapTokenReserves[token] == old(mapTokenReserves[token]) - tokenAmount;
              /// #if_succeeds {:msg "any paused"} paused == 1 || paused == 2;
              function withdraw(address to, uint256 tokenAmount, address token) external returns (bool success) {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check that the withdraw address is not treasury itself
                  if (to == address(this)) {
                      revert TransferFailed(token, address(this), to, tokenAmount);
                  }
                  // Check for the zero withdraw amount
                  if (tokenAmount == 0) {
                      revert ZeroValue();
                  }
                  // ETH address is taken separately, and all the LP tokens must be validated with corresponding token reserves
                  if (token == ETH_TOKEN_ADDRESS) {
                      uint256 amountOwned = ETHOwned;
                      // Check if treasury has enough amount of owned ETH
                      if (amountOwned >= tokenAmount) {
                          // This branch is used to transfer ETH to a specified address
                          amountOwned -= tokenAmount;
                          ETHOwned = uint96(amountOwned);
                          emit Withdraw(ETH_TOKEN_ADDRESS, to, tokenAmount);
                          // Send ETH to the specified address
                          (success, ) = to.call{value: tokenAmount}("");
                          if (!success) {
                              revert TransferFailed(ETH_TOKEN_ADDRESS, address(this), to, tokenAmount);
                          }
                      } else {
                          // Insufficient amount of treasury owned ETH
                          revert LowerThan(tokenAmount, amountOwned);
                      }
                  } else {
                      // Only approved token reserves can be used for redemptions
                      if (!mapEnabledTokens[token]) {
                          revert UnauthorizedToken(token);
                      }
                      // Decrease the global LP token reserves record
                      uint256 reserves = mapTokenReserves[token];
                      if (reserves >= tokenAmount) {
                          reserves -= tokenAmount;
                          mapTokenReserves[token] = reserves;
                          emit Withdraw(token, to, tokenAmount);
                          // Transfer LP tokens
                          // We assume that LP tokens enabled in the protocol are safe by default
                          // UniswapV2ERC20 realization has a standard transfer() function
                          success = IToken(token).transfer(to, tokenAmount);
                          if (!success) {
                              revert TransferFailed(token, address(this), to, tokenAmount);
                          }
                      }  else {
                          // Insufficient amount of LP tokens
                          revert LowerThan(tokenAmount, reserves);
                      }
                  }
              }
              /// @dev Withdraws ETH and / or OLAS amounts to the requested account address.
              /// @notice Only dispenser contract can call this function.
              /// @notice Reentrancy guard is on a dispenser side.
              /// @notice Zero account address is not possible, since the dispenser contract interacts with msg.sender.
              /// @param account Account address.
              /// @param accountRewards Amount of account rewards.
              /// @param accountTopUps Amount of account top-ups.
              /// @return success True if the function execution is successful.
              /// #if_succeeds {:msg "we do not touch the owners balance"} old(ETHOwned) == ETHOwned;
              /// #if_succeeds {:msg "updated ETHFromServices"} accountRewards > 0 && ETHFromServices >= accountRewards ==> ETHFromServices == old(ETHFromServices) - accountRewards;
              /// #if_succeeds {:msg "ETH balance"} accountRewards > 0 && ETHFromServices >= accountRewards ==> address(this).balance == old(address(this).balance) - accountRewards;
              /// #if_succeeds {:msg "updated OLAS balances"} accountTopUps > 0 ==> IToken(olas).balanceOf(account) == old(IToken(olas).balanceOf(account)) + accountTopUps;
              /// #if_succeeds {:msg "OLAS supply"} IToken(olas).totalSupply() == old(IToken(olas).totalSupply()) + accountTopUps;
              /// #if_succeeds {:msg "unpaused"} paused == 1;
              function withdrawToAccount(address account, uint256 accountRewards, uint256 accountTopUps) external
                  returns (bool success)
              {
                  // Check if the contract is paused
                  if (paused == 2) {
                      revert Paused();
                  }
                  // Check for the dispenser access
                  if (dispenser != msg.sender) {
                      revert ManagerOnly(msg.sender, dispenser);
                  }
                  uint256 amountETHFromServices = ETHFromServices;
                  // Send ETH rewards, if any
                  if (accountRewards > 0 && amountETHFromServices >= accountRewards) {
                      amountETHFromServices -= accountRewards;
                      ETHFromServices = uint96(amountETHFromServices);
                      emit Withdraw(ETH_TOKEN_ADDRESS, account, accountRewards);
                      (success, ) = account.call{value: accountRewards}("");
                      if (!success) {
                          revert TransferFailed(address(0), address(this), account, accountRewards);
                      }
                  }
                  // Send OLAS top-ups
                  if (accountTopUps > 0) {
                      // Tokenomics has already accounted for the account's top-up amount,
                      // thus the the mint does not break the inflation schedule
                      IOLAS(olas).mint(account, accountTopUps);
                      success = true;
                      emit Withdraw(olas, account, accountTopUps);
                  }
              }
              /// @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.
              /// #if_succeeds {:msg "we do not touch the total eth balance"} old(address(this).balance) == address(this).balance;
              /// #if_succeeds {:msg "conservation law"} old(ETHFromServices + ETHOwned) == ETHFromServices + ETHOwned;
              /// #if_succeeds {:msg "unpaused"} paused == 1;
              function rebalanceTreasury(uint256 treasuryRewards) external returns (bool success) {
                  // Check if the contract is paused
                  if (paused == 2) {
                      revert Paused();
                  }
                  // Check for the tokenomics contract access
                  if (msg.sender != tokenomics) {
                      revert ManagerOnly(msg.sender, tokenomics);
                  }
                  // Collect treasury's own reward share
                  success = true;
                  if (treasuryRewards > 0) {
                      uint256 amountETHFromServices = ETHFromServices;
                      if (amountETHFromServices >= treasuryRewards) {
                          // Update ETH from services value
                          amountETHFromServices -= treasuryRewards;
                          // Update treasury ETH owned values
                          uint256 amountETHOwned = ETHOwned;
                          amountETHOwned += treasuryRewards;
                          // Assign back to state variables
                          ETHOwned = uint96(amountETHOwned);
                          ETHFromServices = uint96(amountETHFromServices);
                          emit UpdateTreasuryBalances(amountETHOwned, amountETHFromServices);
                      } else {
                          // There is not enough amount from services to allocate to the treasury
                          success = false;
                      }
                  }
              }
              /// @dev Drains slashed funds from the service registry.
              /// @return amount Drained amount.
              /// #if_succeeds {:msg "correct update total eth balance"} address(this).balance == old(address(this).balance) + amount;
              /// #if_succeeds {:msg "conservation law"} ETHFromServices + ETHOwned == old(ETHFromServices + ETHOwned) + amount;
              ///if_succeeds {:msg "slashed funds check"} IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds() >= minAcceptedETH
              /// ==> old(IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds()) == amount;
              function drainServiceSlashedFunds() external returns (uint256 amount) {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Get the service registry contract address
                  address serviceRegistry = ITokenomics(tokenomics).serviceRegistry();
                  // Check if the amount of slashed funds are at least the minimum required amount to receive by the Treasury
                  uint256 slashedFunds = IServiceRegistry(serviceRegistry).slashedFunds();
                  if (slashedFunds < minAcceptedETH) {
                      revert LowerThan(slashedFunds, minAcceptedETH);
                  }
                  // Call the service registry drain function
                  amount = IServiceRegistry(serviceRegistry).drain();
              }
              /// @dev Enables an LP token to be bonded for OLAS.
              /// @param token Token address.
              function enableToken(address token) external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for the zero address token
                  if (token == address(0)) {
                      revert ZeroAddress();
                  }
                  // Authorize the token
                  if (!mapEnabledTokens[token]) {
                      mapEnabledTokens[token] = true;
                      emit EnableToken(token);
                  }
              }
              /// @dev Disables an LP token from the ability to bond for OLAS.
              /// @param token Token address.
              function disableToken(address token) external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  if (mapEnabledTokens[token]) {
                      // The reserves of a token must be zero in order to disable it
                      if (mapTokenReserves[token] > 0) {
                          revert NonZeroValue();
                      }
                      mapEnabledTokens[token] = false;
                      emit DisableToken(token);
                  }
              }
              /// @dev Gets information about token being enabled for bonding.
              /// @param token Token address.
              /// @return enabled True if token is enabled.
              function isEnabled(address token) external view returns (bool enabled) {
                  enabled = mapEnabledTokens[token];
              }
              /// @dev Pauses the contract.
              function pause() external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  paused = 2;
                  emit PauseTreasury();
              }
              /// @dev Unpauses the contract.
              function unpause() external {
                  // Check for the contract ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  paused = 1;
                  emit UnpauseTreasury();
              }
          }
          

          File 3 of 7: OLAS
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "../lib/solmate/src/tokens/ERC20.sol";
          /// @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 Provided zero address.
          error ZeroAddress();
          /// @title OLAS - Smart contract for the OLAS token.
          /// @author AL
          /// @author Aleksandr Kuperman - <[email protected]>
          contract OLAS is ERC20 {
              event MinterUpdated(address indexed minter);
              event OwnerUpdated(address indexed owner);
              // One year interval
              uint256 public constant oneYear = 1 days * 365;
              // Total supply cap for the first ten years (one billion OLAS tokens)
              uint256 public constant tenYearSupplyCap = 1_000_000_000e18;
              // Maximum annual inflation after first ten years
              uint256 public constant maxMintCapFraction = 2;
              // Initial timestamp of the token deployment
              uint256 public immutable timeLaunch;
              // Owner address
              address public owner;
              // Minter address
              address public minter;
              constructor() ERC20("Autonolas", "OLAS", 18) {
                  owner = msg.sender;
                  minter = msg.sender;
                  timeLaunch = block.timestamp;
              }
              /// @dev Changes the owner address.
              /// @param newOwner Address of a new owner.
              function changeOwner(address newOwner) external {
                  if (msg.sender != owner) {
                      revert ManagerOnly(msg.sender, owner);
                  }
                  if (newOwner == address(0)) {
                      revert ZeroAddress();
                  }
                  owner = newOwner;
                  emit OwnerUpdated(newOwner);
              }
              /// @dev Changes the minter address.
              /// @param newMinter Address of a new minter.
              function changeMinter(address newMinter) external {
                  if (msg.sender != owner) {
                      revert ManagerOnly(msg.sender, owner);
                  }
                  if (newMinter == address(0)) {
                      revert ZeroAddress();
                  }
                  minter = newMinter;
                  emit MinterUpdated(newMinter);
              }
              /// @dev Mints OLAS tokens.
              /// @param account Account address.
              /// @param amount OLAS token amount.
              function mint(address account, uint256 amount) external {
                  // Access control
                  if (msg.sender != minter) {
                      revert ManagerOnly(msg.sender, minter);
                  }
                  // Check the inflation schedule and mint
                  if (inflationControl(amount)) {
                      _mint(account, amount);
                  }
              }
              /// @dev Provides various checks for the inflation control.
              /// @param amount Amount of OLAS to mint.
              /// @return True if the amount request is within inflation boundaries.
              function inflationControl(uint256 amount) public view returns (bool) {
                  uint256 remainder = inflationRemainder();
                  return (amount <= remainder);
              }
              /// @dev Gets the reminder of OLAS possible for the mint.
              /// @return remainder OLAS token remainder.
              function inflationRemainder() public view returns (uint256 remainder) {
                  uint256 _totalSupply = totalSupply;
                  // Current year
                  uint256 numYears = (block.timestamp - timeLaunch) / oneYear;
                  // Calculate maximum mint amount to date
                  uint256 supplyCap = tenYearSupplyCap;
                  // After 10 years, adjust supplyCap according to the yearly inflation % set in maxMintCapFraction
                  if (numYears > 9) {
                      // Number of years after ten years have passed (including ongoing ones)
                      numYears -= 9;
                      for (uint256 i = 0; i < numYears; ++i) {
                          supplyCap += (supplyCap * maxMintCapFraction) / 100;
                      }
                  }
                  // Check for the requested mint overflow
                  remainder = supplyCap - _totalSupply;
              }
              /// @dev Burns OLAS tokens.
              /// @param amount OLAS token amount to burn.
              function burn(uint256 amount) external {
                  _burn(msg.sender, amount);
              }
              /// @dev Decreases the allowance of another account over their tokens.
              /// @param spender Account that tokens are approved for.
              /// @param amount Amount to decrease approval by.
              /// @return True if the operation succeeded.
              function decreaseAllowance(address spender, uint256 amount) external returns (bool) {
                  uint256 spenderAllowance = allowance[msg.sender][spender];
                  if (spenderAllowance != type(uint256).max) {
                      spenderAllowance -= amount;
                      allowance[msg.sender][spender] = spenderAllowance;
                      emit Approval(msg.sender, spender, spenderAllowance);
                  }
                  return true;
              }
              /// @dev Increases the allowance of another account over their tokens.
              /// @param spender Account that tokens are approved for.
              /// @param amount Amount to increase approval by.
              /// @return True if the operation succeeded.
              function increaseAllowance(address spender, uint256 amount) external returns (bool) {
                  uint256 spenderAllowance = allowance[msg.sender][spender];
                  spenderAllowance += amount;
                  allowance[msg.sender][spender] = spenderAllowance;
                  emit Approval(msg.sender, spender, spenderAllowance);
                  return true;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.0;
          /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
          /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
          /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
          /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
          abstract contract ERC20 {
              /*//////////////////////////////////////////////////////////////
                                           EVENTS
              //////////////////////////////////////////////////////////////*/
              event Transfer(address indexed from, address indexed to, uint256 amount);
              event Approval(address indexed owner, address indexed spender, uint256 amount);
              /*//////////////////////////////////////////////////////////////
                                      METADATA STORAGE
              //////////////////////////////////////////////////////////////*/
              string public name;
              string public symbol;
              uint8 public immutable decimals;
              /*//////////////////////////////////////////////////////////////
                                        ERC20 STORAGE
              //////////////////////////////////////////////////////////////*/
              uint256 public totalSupply;
              mapping(address => uint256) public balanceOf;
              mapping(address => mapping(address => uint256)) public allowance;
              /*//////////////////////////////////////////////////////////////
                                      EIP-2612 STORAGE
              //////////////////////////////////////////////////////////////*/
              uint256 internal immutable INITIAL_CHAIN_ID;
              bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
              mapping(address => uint256) public nonces;
              /*//////////////////////////////////////////////////////////////
                                         CONSTRUCTOR
              //////////////////////////////////////////////////////////////*/
              constructor(
                  string memory _name,
                  string memory _symbol,
                  uint8 _decimals
              ) {
                  name = _name;
                  symbol = _symbol;
                  decimals = _decimals;
                  INITIAL_CHAIN_ID = block.chainid;
                  INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
              }
              /*//////////////////////////////////////////////////////////////
                                         ERC20 LOGIC
              //////////////////////////////////////////////////////////////*/
              function approve(address spender, uint256 amount) public virtual returns (bool) {
                  allowance[msg.sender][spender] = amount;
                  emit Approval(msg.sender, spender, amount);
                  return true;
              }
              function transfer(address to, uint256 amount) public virtual returns (bool) {
                  balanceOf[msg.sender] -= amount;
                  // Cannot overflow because the sum of all user
                  // balances can't exceed the max uint256 value.
                  unchecked {
                      balanceOf[to] += amount;
                  }
                  emit Transfer(msg.sender, to, amount);
                  return true;
              }
              function transferFrom(
                  address from,
                  address to,
                  uint256 amount
              ) public virtual returns (bool) {
                  uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
                  if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
                  balanceOf[from] -= amount;
                  // Cannot overflow because the sum of all user
                  // balances can't exceed the max uint256 value.
                  unchecked {
                      balanceOf[to] += amount;
                  }
                  emit Transfer(from, to, amount);
                  return true;
              }
              /*//////////////////////////////////////////////////////////////
                                       EIP-2612 LOGIC
              //////////////////////////////////////////////////////////////*/
              function permit(
                  address owner,
                  address spender,
                  uint256 value,
                  uint256 deadline,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) public virtual {
                  require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
                  // Unchecked because the only math done is incrementing
                  // the owner's nonce which cannot realistically overflow.
                  unchecked {
                      address recoveredAddress = ecrecover(
                          keccak256(
                              abi.encodePacked(
                                  "\\x19\\x01",
                                  DOMAIN_SEPARATOR(),
                                  keccak256(
                                      abi.encode(
                                          keccak256(
                                              "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                          ),
                                          owner,
                                          spender,
                                          value,
                                          nonces[owner]++,
                                          deadline
                                      )
                                  )
                              )
                          ),
                          v,
                          r,
                          s
                      );
                      require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
                      allowance[recoveredAddress][spender] = value;
                  }
                  emit Approval(owner, spender, value);
              }
              function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
                  return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
              }
              function computeDomainSeparator() internal view virtual returns (bytes32) {
                  return
                      keccak256(
                          abi.encode(
                              keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                              keccak256(bytes(name)),
                              keccak256("1"),
                              block.chainid,
                              address(this)
                          )
                      );
              }
              /*//////////////////////////////////////////////////////////////
                                  INTERNAL MINT/BURN LOGIC
              //////////////////////////////////////////////////////////////*/
              function _mint(address to, uint256 amount) internal virtual {
                  totalSupply += amount;
                  // Cannot overflow because the sum of all user
                  // balances can't exceed the max uint256 value.
                  unchecked {
                      balanceOf[to] += amount;
                  }
                  emit Transfer(address(0), to, amount);
              }
              function _burn(address from, uint256 amount) internal virtual {
                  balanceOf[from] -= amount;
                  // Cannot underflow because a user's balance
                  // will never be larger than the total supply.
                  unchecked {
                      totalSupply -= amount;
                  }
                  emit Transfer(from, address(0), amount);
              }
          }
          

          File 4 of 7: TokenomicsProxy
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev Proxy initialization failed.
          error InitializationFailed();
          /// @dev Zero master tokenomics address.
          error ZeroTokenomicsAddress();
          /// @dev Zero tokenomics initialization data.
          error ZeroTokenomicsData();
          /*
          * This is a proxy contract for tokenomics.
          * Proxy implementation is created based on the Universal Upgradeable Proxy Standard (UUPS) EIP-1822.
          * The implementation address must be located in a unique storage slot of the proxy contract.
          * The upgrade logic must be located in the implementation contract.
          * Special tokenomics implementation address slot is produced by hashing the "PROXY_TOKENOMICS" string in order to make
          * the slot unique.
          * The fallback() implementation for all the delegatecall-s is inspired by the Gnosis Safe set of contracts.
          */
          /// @title TokenomicsProxy - Smart contract for tokenomics proxy
          /// @author AL
          /// @author Aleksandr Kuperman - <[email protected]>
          contract TokenomicsProxy {
              // Code position in storage is keccak256("PROXY_TOKENOMICS") = "0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f"
              bytes32 public constant PROXY_TOKENOMICS = 0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f;
              /// @dev TokenomicsProxy constructor.
              /// @param tokenomics Tokenomics implementation address.
              /// @param tokenomicsData Tokenomics initialization data.
              constructor(address tokenomics, bytes memory tokenomicsData) {
                  // Check for the zero address, since the delegatecall works even with the zero one
                  if (tokenomics == address(0)) {
                      revert ZeroTokenomicsAddress();
                  }
                  // Check for the zero data
                  if (tokenomicsData.length == 0) {
                      revert ZeroTokenomicsData();
                  }
                  assembly {
                      sstore(PROXY_TOKENOMICS, tokenomics)
                  }
                  // Initialize proxy tokenomics storage
                  (bool success, ) = tokenomics.delegatecall(tokenomicsData);
                  if (!success) {
                      revert InitializationFailed();
                  }
              }
              /// @dev Delegatecall to all the incoming data.
              fallback() external {
                  assembly {
                      let tokenomics := sload(PROXY_TOKENOMICS)
                      // Otherwise continue with the delegatecall to the tokenomics implementation
                      calldatacopy(0, 0, calldatasize())
                      let success := delegatecall(gas(), tokenomics, 0, calldatasize(), 0, 0)
                      returndatacopy(0, 0, returndatasize())
                      if eq(success, 0) {
                          revert(0, returndatasize())
                      }
                      return(0, returndatasize())
                  }
              }
          }
          

          File 5 of 7: Tokenomics
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          // Common.sol
          //
          // Common mathematical functions used in both SD59x18 and UD60x18. Note that these global functions do not
          // always operate with SD59x18 and UD60x18 numbers.
          /*//////////////////////////////////////////////////////////////////////////
                                          CUSTOM ERRORS
          //////////////////////////////////////////////////////////////////////////*/
          /// @notice Thrown when the resultant value in {mulDiv} overflows uint256.
          error PRBMath_MulDiv_Overflow(uint256 x, uint256 y, uint256 denominator);
          /// @notice Thrown when the resultant value in {mulDiv18} overflows uint256.
          error PRBMath_MulDiv18_Overflow(uint256 x, uint256 y);
          /// @notice Thrown when one of the inputs passed to {mulDivSigned} is `type(int256).min`.
          error PRBMath_MulDivSigned_InputTooSmall();
          /// @notice Thrown when the resultant value in {mulDivSigned} overflows int256.
          error PRBMath_MulDivSigned_Overflow(int256 x, int256 y);
          /*//////////////////////////////////////////////////////////////////////////
                                              CONSTANTS
          //////////////////////////////////////////////////////////////////////////*/
          /// @dev The maximum value a uint128 number can have.
          uint128 constant MAX_UINT128 = type(uint128).max;
          /// @dev The maximum value a uint40 number can have.
          uint40 constant MAX_UINT40 = type(uint40).max;
          /// @dev The unit number, which the decimal precision of the fixed-point types.
          uint256 constant UNIT = 1e18;
          /// @dev The unit number inverted mod 2^256.
          uint256 constant UNIT_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281;
          /// @dev The the largest power of two that divides the decimal value of `UNIT`. The logarithm of this value is the least significant
          /// bit in the binary representation of `UNIT`.
          uint256 constant UNIT_LPOTD = 262144;
          /*//////////////////////////////////////////////////////////////////////////
                                              FUNCTIONS
          //////////////////////////////////////////////////////////////////////////*/
          /// @notice Calculates the binary exponent of x using the binary fraction method.
          /// @dev Has to use 192.64-bit fixed-point numbers. See https://ethereum.stackexchange.com/a/96594/24693.
          /// @param x The exponent as an unsigned 192.64-bit fixed-point number.
          /// @return result The result as an unsigned 60.18-decimal fixed-point number.
          /// @custom:smtchecker abstract-function-nondet
          function exp2(uint256 x) pure returns (uint256 result) {
              unchecked {
                  // Start from 0.5 in the 192.64-bit fixed-point format.
                  result = 0x800000000000000000000000000000000000000000000000;
                  // The following logic multiplies the result by $\\sqrt{2^{-i}}$ when the bit at position i is 1. Key points:
                  //
                  // 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65.
                  // 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing
                  // a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1,
                  // we know that `x & 0xFF` is also 1.
                  if (x & 0xFF00000000000000 > 0) {
                      if (x & 0x8000000000000000 > 0) {
                          result = (result * 0x16A09E667F3BCC909) >> 64;
                      }
                      if (x & 0x4000000000000000 > 0) {
                          result = (result * 0x1306FE0A31B7152DF) >> 64;
                      }
                      if (x & 0x2000000000000000 > 0) {
                          result = (result * 0x1172B83C7D517ADCE) >> 64;
                      }
                      if (x & 0x1000000000000000 > 0) {
                          result = (result * 0x10B5586CF9890F62A) >> 64;
                      }
                      if (x & 0x800000000000000 > 0) {
                          result = (result * 0x1059B0D31585743AE) >> 64;
                      }
                      if (x & 0x400000000000000 > 0) {
                          result = (result * 0x102C9A3E778060EE7) >> 64;
                      }
                      if (x & 0x200000000000000 > 0) {
                          result = (result * 0x10163DA9FB33356D8) >> 64;
                      }
                      if (x & 0x100000000000000 > 0) {
                          result = (result * 0x100B1AFA5ABCBED61) >> 64;
                      }
                  }
                  if (x & 0xFF000000000000 > 0) {
                      if (x & 0x80000000000000 > 0) {
                          result = (result * 0x10058C86DA1C09EA2) >> 64;
                      }
                      if (x & 0x40000000000000 > 0) {
                          result = (result * 0x1002C605E2E8CEC50) >> 64;
                      }
                      if (x & 0x20000000000000 > 0) {
                          result = (result * 0x100162F3904051FA1) >> 64;
                      }
                      if (x & 0x10000000000000 > 0) {
                          result = (result * 0x1000B175EFFDC76BA) >> 64;
                      }
                      if (x & 0x8000000000000 > 0) {
                          result = (result * 0x100058BA01FB9F96D) >> 64;
                      }
                      if (x & 0x4000000000000 > 0) {
                          result = (result * 0x10002C5CC37DA9492) >> 64;
                      }
                      if (x & 0x2000000000000 > 0) {
                          result = (result * 0x1000162E525EE0547) >> 64;
                      }
                      if (x & 0x1000000000000 > 0) {
                          result = (result * 0x10000B17255775C04) >> 64;
                      }
                  }
                  if (x & 0xFF0000000000 > 0) {
                      if (x & 0x800000000000 > 0) {
                          result = (result * 0x1000058B91B5BC9AE) >> 64;
                      }
                      if (x & 0x400000000000 > 0) {
                          result = (result * 0x100002C5C89D5EC6D) >> 64;
                      }
                      if (x & 0x200000000000 > 0) {
                          result = (result * 0x10000162E43F4F831) >> 64;
                      }
                      if (x & 0x100000000000 > 0) {
                          result = (result * 0x100000B1721BCFC9A) >> 64;
                      }
                      if (x & 0x80000000000 > 0) {
                          result = (result * 0x10000058B90CF1E6E) >> 64;
                      }
                      if (x & 0x40000000000 > 0) {
                          result = (result * 0x1000002C5C863B73F) >> 64;
                      }
                      if (x & 0x20000000000 > 0) {
                          result = (result * 0x100000162E430E5A2) >> 64;
                      }
                      if (x & 0x10000000000 > 0) {
                          result = (result * 0x1000000B172183551) >> 64;
                      }
                  }
                  if (x & 0xFF00000000 > 0) {
                      if (x & 0x8000000000 > 0) {
                          result = (result * 0x100000058B90C0B49) >> 64;
                      }
                      if (x & 0x4000000000 > 0) {
                          result = (result * 0x10000002C5C8601CC) >> 64;
                      }
                      if (x & 0x2000000000 > 0) {
                          result = (result * 0x1000000162E42FFF0) >> 64;
                      }
                      if (x & 0x1000000000 > 0) {
                          result = (result * 0x10000000B17217FBB) >> 64;
                      }
                      if (x & 0x800000000 > 0) {
                          result = (result * 0x1000000058B90BFCE) >> 64;
                      }
                      if (x & 0x400000000 > 0) {
                          result = (result * 0x100000002C5C85FE3) >> 64;
                      }
                      if (x & 0x200000000 > 0) {
                          result = (result * 0x10000000162E42FF1) >> 64;
                      }
                      if (x & 0x100000000 > 0) {
                          result = (result * 0x100000000B17217F8) >> 64;
                      }
                  }
                  if (x & 0xFF000000 > 0) {
                      if (x & 0x80000000 > 0) {
                          result = (result * 0x10000000058B90BFC) >> 64;
                      }
                      if (x & 0x40000000 > 0) {
                          result = (result * 0x1000000002C5C85FE) >> 64;
                      }
                      if (x & 0x20000000 > 0) {
                          result = (result * 0x100000000162E42FF) >> 64;
                      }
                      if (x & 0x10000000 > 0) {
                          result = (result * 0x1000000000B17217F) >> 64;
                      }
                      if (x & 0x8000000 > 0) {
                          result = (result * 0x100000000058B90C0) >> 64;
                      }
                      if (x & 0x4000000 > 0) {
                          result = (result * 0x10000000002C5C860) >> 64;
                      }
                      if (x & 0x2000000 > 0) {
                          result = (result * 0x1000000000162E430) >> 64;
                      }
                      if (x & 0x1000000 > 0) {
                          result = (result * 0x10000000000B17218) >> 64;
                      }
                  }
                  if (x & 0xFF0000 > 0) {
                      if (x & 0x800000 > 0) {
                          result = (result * 0x1000000000058B90C) >> 64;
                      }
                      if (x & 0x400000 > 0) {
                          result = (result * 0x100000000002C5C86) >> 64;
                      }
                      if (x & 0x200000 > 0) {
                          result = (result * 0x10000000000162E43) >> 64;
                      }
                      if (x & 0x100000 > 0) {
                          result = (result * 0x100000000000B1721) >> 64;
                      }
                      if (x & 0x80000 > 0) {
                          result = (result * 0x10000000000058B91) >> 64;
                      }
                      if (x & 0x40000 > 0) {
                          result = (result * 0x1000000000002C5C8) >> 64;
                      }
                      if (x & 0x20000 > 0) {
                          result = (result * 0x100000000000162E4) >> 64;
                      }
                      if (x & 0x10000 > 0) {
                          result = (result * 0x1000000000000B172) >> 64;
                      }
                  }
                  if (x & 0xFF00 > 0) {
                      if (x & 0x8000 > 0) {
                          result = (result * 0x100000000000058B9) >> 64;
                      }
                      if (x & 0x4000 > 0) {
                          result = (result * 0x10000000000002C5D) >> 64;
                      }
                      if (x & 0x2000 > 0) {
                          result = (result * 0x1000000000000162E) >> 64;
                      }
                      if (x & 0x1000 > 0) {
                          result = (result * 0x10000000000000B17) >> 64;
                      }
                      if (x & 0x800 > 0) {
                          result = (result * 0x1000000000000058C) >> 64;
                      }
                      if (x & 0x400 > 0) {
                          result = (result * 0x100000000000002C6) >> 64;
                      }
                      if (x & 0x200 > 0) {
                          result = (result * 0x10000000000000163) >> 64;
                      }
                      if (x & 0x100 > 0) {
                          result = (result * 0x100000000000000B1) >> 64;
                      }
                  }
                  if (x & 0xFF > 0) {
                      if (x & 0x80 > 0) {
                          result = (result * 0x10000000000000059) >> 64;
                      }
                      if (x & 0x40 > 0) {
                          result = (result * 0x1000000000000002C) >> 64;
                      }
                      if (x & 0x20 > 0) {
                          result = (result * 0x10000000000000016) >> 64;
                      }
                      if (x & 0x10 > 0) {
                          result = (result * 0x1000000000000000B) >> 64;
                      }
                      if (x & 0x8 > 0) {
                          result = (result * 0x10000000000000006) >> 64;
                      }
                      if (x & 0x4 > 0) {
                          result = (result * 0x10000000000000003) >> 64;
                      }
                      if (x & 0x2 > 0) {
                          result = (result * 0x10000000000000001) >> 64;
                      }
                      if (x & 0x1 > 0) {
                          result = (result * 0x10000000000000001) >> 64;
                      }
                  }
                  // In the code snippet below, two operations are executed simultaneously:
                  //
                  // 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1
                  // accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192.
                  // 2. The result is then converted to an unsigned 60.18-decimal fixed-point format.
                  //
                  // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the,
                  // integer part, $2^n$.
                  result *= UNIT;
                  result >>= (191 - (x >> 64));
              }
          }
          /// @notice Finds the zero-based index of the first 1 in the binary representation of x.
          ///
          /// @dev See the note on "msb" in this Wikipedia article: https://en.wikipedia.org/wiki/Find_first_set
          ///
          /// Each step in this implementation is equivalent to this high-level code:
          ///
          /// ```solidity
          /// if (x >= 2 ** 128) {
          ///     x >>= 128;
          ///     result += 128;
          /// }
          /// ```
          ///
          /// Where 128 is replaced with each respective power of two factor. See the full high-level implementation here:
          /// https://gist.github.com/PaulRBerg/f932f8693f2733e30c4d479e8e980948
          ///
          /// The Yul instructions used below are:
          ///
          /// - "gt" is "greater than"
          /// - "or" is the OR bitwise operator
          /// - "shl" is "shift left"
          /// - "shr" is "shift right"
          ///
          /// @param x The uint256 number for which to find the index of the most significant bit.
          /// @return result The index of the most significant bit as a uint256.
          /// @custom:smtchecker abstract-function-nondet
          function msb(uint256 x) pure returns (uint256 result) {
              // 2^128
              assembly ("memory-safe") {
                  let factor := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^64
              assembly ("memory-safe") {
                  let factor := shl(6, gt(x, 0xFFFFFFFFFFFFFFFF))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^32
              assembly ("memory-safe") {
                  let factor := shl(5, gt(x, 0xFFFFFFFF))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^16
              assembly ("memory-safe") {
                  let factor := shl(4, gt(x, 0xFFFF))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^8
              assembly ("memory-safe") {
                  let factor := shl(3, gt(x, 0xFF))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^4
              assembly ("memory-safe") {
                  let factor := shl(2, gt(x, 0xF))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^2
              assembly ("memory-safe") {
                  let factor := shl(1, gt(x, 0x3))
                  x := shr(factor, x)
                  result := or(result, factor)
              }
              // 2^1
              // No need to shift x any more.
              assembly ("memory-safe") {
                  let factor := gt(x, 0x1)
                  result := or(result, factor)
              }
          }
          /// @notice Calculates x*y÷denominator with 512-bit precision.
          ///
          /// @dev Credits to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv.
          ///
          /// Notes:
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - The denominator must not be zero.
          /// - The result must fit in uint256.
          ///
          /// @param x The multiplicand as a uint256.
          /// @param y The multiplier as a uint256.
          /// @param denominator The divisor as a uint256.
          /// @return result The result as a uint256.
          /// @custom:smtchecker abstract-function-nondet
          function mulDiv(uint256 x, uint256 y, uint256 denominator) pure returns (uint256 result) {
              // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
              // use the Chinese Remainder Theorem to reconstruct the 512-bit result. The result is stored in two 256
              // variables such that product = prod1 * 2^256 + prod0.
              uint256 prod0; // Least significant 256 bits of the product
              uint256 prod1; // Most significant 256 bits of the product
              assembly ("memory-safe") {
                  let mm := mulmod(x, y, not(0))
                  prod0 := mul(x, y)
                  prod1 := sub(sub(mm, prod0), lt(mm, prod0))
              }
              // Handle non-overflow cases, 256 by 256 division.
              if (prod1 == 0) {
                  unchecked {
                      return prod0 / denominator;
                  }
              }
              // Make sure the result is less than 2^256. Also prevents denominator == 0.
              if (prod1 >= denominator) {
                  revert PRBMath_MulDiv_Overflow(x, y, denominator);
              }
              ////////////////////////////////////////////////////////////////////////////
              // 512 by 256 division
              ////////////////////////////////////////////////////////////////////////////
              // Make division exact by subtracting the remainder from [prod1 prod0].
              uint256 remainder;
              assembly ("memory-safe") {
                  // Compute remainder using the mulmod Yul instruction.
                  remainder := mulmod(x, y, denominator)
                  // Subtract 256 bit number from 512-bit number.
                  prod1 := sub(prod1, gt(remainder, prod0))
                  prod0 := sub(prod0, remainder)
              }
              unchecked {
                  // Calculate the largest power of two divisor of the denominator using the unary operator ~. This operation cannot overflow
                  // because the denominator cannot be zero at this point in the function execution. The result is always >= 1.
                  // For more detail, see https://cs.stackexchange.com/q/138556/92363.
                  uint256 lpotdod = denominator & (~denominator + 1);
                  uint256 flippedLpotdod;
                  assembly ("memory-safe") {
                      // Factor powers of two out of denominator.
                      denominator := div(denominator, lpotdod)
                      // Divide [prod1 prod0] by lpotdod.
                      prod0 := div(prod0, lpotdod)
                      // Get the flipped value `2^256 / lpotdod`. If the `lpotdod` is zero, the flipped value is one.
                      // `sub(0, lpotdod)` produces the two's complement version of `lpotdod`, which is equivalent to flipping all the bits.
                      // However, `div` interprets this value as an unsigned value: https://ethereum.stackexchange.com/q/147168/24693
                      flippedLpotdod := add(div(sub(0, lpotdod), lpotdod), 1)
                  }
                  // Shift in bits from prod1 into prod0.
                  prod0 |= prod1 * flippedLpotdod;
                  // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                  // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                  // four bits. That is, denominator * inv = 1 mod 2^4.
                  uint256 inverse = (3 * denominator) ^ 2;
                  // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                  // in modular arithmetic, doubling the correct bits in each step.
                  inverse *= 2 - denominator * inverse; // inverse mod 2^8
                  inverse *= 2 - denominator * inverse; // inverse mod 2^16
                  inverse *= 2 - denominator * inverse; // inverse mod 2^32
                  inverse *= 2 - denominator * inverse; // inverse mod 2^64
                  inverse *= 2 - denominator * inverse; // inverse mod 2^128
                  inverse *= 2 - denominator * inverse; // inverse mod 2^256
                  // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                  // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                  // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                  // is no longer required.
                  result = prod0 * inverse;
              }
          }
          /// @notice Calculates x*y÷1e18 with 512-bit precision.
          ///
          /// @dev A variant of {mulDiv} with constant folding, i.e. in which the denominator is hard coded to 1e18.
          ///
          /// Notes:
          /// - The body is purposely left uncommented; to understand how this works, see the documentation in {mulDiv}.
          /// - The result is rounded toward zero.
          /// - We take as an axiom that the result cannot be `MAX_UINT256` when x and y solve the following system of equations:
          ///
          /// $$
          /// \\begin{cases}
          ///     x * y = MAX\\_UINT256 * UNIT \\\\
          ///     (x * y) \\% UNIT \\geq \\frac{UNIT}{2}
          /// \\end{cases}
          /// $$
          ///
          /// Requirements:
          /// - Refer to the requirements in {mulDiv}.
          /// - The result must fit in uint256.
          ///
          /// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number.
          /// @param y The multiplier as an unsigned 60.18-decimal fixed-point number.
          /// @return result The result as an unsigned 60.18-decimal fixed-point number.
          /// @custom:smtchecker abstract-function-nondet
          function mulDiv18(uint256 x, uint256 y) pure returns (uint256 result) {
              uint256 prod0;
              uint256 prod1;
              assembly ("memory-safe") {
                  let mm := mulmod(x, y, not(0))
                  prod0 := mul(x, y)
                  prod1 := sub(sub(mm, prod0), lt(mm, prod0))
              }
              if (prod1 == 0) {
                  unchecked {
                      return prod0 / UNIT;
                  }
              }
              if (prod1 >= UNIT) {
                  revert PRBMath_MulDiv18_Overflow(x, y);
              }
              uint256 remainder;
              assembly ("memory-safe") {
                  remainder := mulmod(x, y, UNIT)
                  result :=
                      mul(
                          or(
                              div(sub(prod0, remainder), UNIT_LPOTD),
                              mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, UNIT_LPOTD), UNIT_LPOTD), 1))
                          ),
                          UNIT_INVERSE
                      )
              }
          }
          /// @notice Calculates x*y÷denominator with 512-bit precision.
          ///
          /// @dev This is an extension of {mulDiv} for signed numbers, which works by computing the signs and the absolute values separately.
          ///
          /// Notes:
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - Refer to the requirements in {mulDiv}.
          /// - None of the inputs can be `type(int256).min`.
          /// - The result must fit in int256.
          ///
          /// @param x The multiplicand as an int256.
          /// @param y The multiplier as an int256.
          /// @param denominator The divisor as an int256.
          /// @return result The result as an int256.
          /// @custom:smtchecker abstract-function-nondet
          function mulDivSigned(int256 x, int256 y, int256 denominator) pure returns (int256 result) {
              if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) {
                  revert PRBMath_MulDivSigned_InputTooSmall();
              }
              // Get hold of the absolute values of x, y and the denominator.
              uint256 xAbs;
              uint256 yAbs;
              uint256 dAbs;
              unchecked {
                  xAbs = x < 0 ? uint256(-x) : uint256(x);
                  yAbs = y < 0 ? uint256(-y) : uint256(y);
                  dAbs = denominator < 0 ? uint256(-denominator) : uint256(denominator);
              }
              // Compute the absolute value of x*y÷denominator. The result must fit in int256.
              uint256 resultAbs = mulDiv(xAbs, yAbs, dAbs);
              if (resultAbs > uint256(type(int256).max)) {
                  revert PRBMath_MulDivSigned_Overflow(x, y);
              }
              // Get the signs of x, y and the denominator.
              uint256 sx;
              uint256 sy;
              uint256 sd;
              assembly ("memory-safe") {
                  // "sgt" is the "signed greater than" assembly instruction and "sub(0,1)" is -1 in two's complement.
                  sx := sgt(x, sub(0, 1))
                  sy := sgt(y, sub(0, 1))
                  sd := sgt(denominator, sub(0, 1))
              }
              // XOR over sx, sy and sd. What this does is to check whether there are 1 or 3 negative signs in the inputs.
              // If there are, the result should be negative. Otherwise, it should be positive.
              unchecked {
                  result = sx ^ sy ^ sd == 0 ? -int256(resultAbs) : int256(resultAbs);
              }
          }
          /// @notice Calculates the square root of x using the Babylonian method.
          ///
          /// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
          ///
          /// Notes:
          /// - If x is not a perfect square, the result is rounded down.
          /// - Credits to OpenZeppelin for the explanations in comments below.
          ///
          /// @param x The uint256 number for which to calculate the square root.
          /// @return result The result as a uint256.
          /// @custom:smtchecker abstract-function-nondet
          function sqrt(uint256 x) pure returns (uint256 result) {
              if (x == 0) {
                  return 0;
              }
              // For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.
              //
              // We know that the "msb" (most significant bit) of x is a power of 2 such that we have:
              //
              // $$
              // msb(x) <= x <= 2*msb(x)$
              // $$
              //
              // We write $msb(x)$ as $2^k$, and we get:
              //
              // $$
              // k = log_2(x)
              // $$
              //
              // Thus, we can write the initial inequality as:
              //
              // $$
              // 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\\\
              // sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\\\
              // 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}
              // $$
              //
              // Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.
              uint256 xAux = uint256(x);
              result = 1;
              if (xAux >= 2 ** 128) {
                  xAux >>= 128;
                  result <<= 64;
              }
              if (xAux >= 2 ** 64) {
                  xAux >>= 64;
                  result <<= 32;
              }
              if (xAux >= 2 ** 32) {
                  xAux >>= 32;
                  result <<= 16;
              }
              if (xAux >= 2 ** 16) {
                  xAux >>= 16;
                  result <<= 8;
              }
              if (xAux >= 2 ** 8) {
                  xAux >>= 8;
                  result <<= 4;
              }
              if (xAux >= 2 ** 4) {
                  xAux >>= 4;
                  result <<= 2;
              }
              if (xAux >= 2 ** 2) {
                  result <<= 1;
              }
              // At this point, `result` is an estimation with at least one bit of precision. We know the true value has at
              // most 128 bits, since it is the square root of a uint256. Newton's method converges quadratically (precision
              // doubles at every iteration). We thus need at most 7 iteration to turn our partial result with one bit of
              // precision into the expected uint128 result.
              unchecked {
                  result = (result + x / result) >> 1;
                  result = (result + x / result) >> 1;
                  result = (result + x / result) >> 1;
                  result = (result + x / result) >> 1;
                  result = (result + x / result) >> 1;
                  result = (result + x / result) >> 1;
                  result = (result + x / result) >> 1;
                  // If x is not a perfect square, round the result toward zero.
                  uint256 roundedResult = x / result;
                  if (result >= roundedResult) {
                      result = roundedResult;
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "../Common.sol" as Common;
          import "./Errors.sol" as CastingErrors;
          import { SD59x18 } from "../sd59x18/ValueType.sol";
          import { UD2x18 } from "../ud2x18/ValueType.sol";
          import { UD60x18 } from "../ud60x18/ValueType.sol";
          import { SD1x18 } from "./ValueType.sol";
          /// @notice Casts an SD1x18 number into SD59x18.
          /// @dev There is no overflow check because the domain of SD1x18 is a subset of SD59x18.
          function intoSD59x18(SD1x18 x) pure returns (SD59x18 result) {
              result = SD59x18.wrap(int256(SD1x18.unwrap(x)));
          }
          /// @notice Casts an SD1x18 number into UD2x18.
          /// - x must be positive.
          function intoUD2x18(SD1x18 x) pure returns (UD2x18 result) {
              int64 xInt = SD1x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD1x18_ToUD2x18_Underflow(x);
              }
              result = UD2x18.wrap(uint64(xInt));
          }
          /// @notice Casts an SD1x18 number into UD60x18.
          /// @dev Requirements:
          /// - x must be positive.
          function intoUD60x18(SD1x18 x) pure returns (UD60x18 result) {
              int64 xInt = SD1x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD1x18_ToUD60x18_Underflow(x);
              }
              result = UD60x18.wrap(uint64(xInt));
          }
          /// @notice Casts an SD1x18 number into uint256.
          /// @dev Requirements:
          /// - x must be positive.
          function intoUint256(SD1x18 x) pure returns (uint256 result) {
              int64 xInt = SD1x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD1x18_ToUint256_Underflow(x);
              }
              result = uint256(uint64(xInt));
          }
          /// @notice Casts an SD1x18 number into uint128.
          /// @dev Requirements:
          /// - x must be positive.
          function intoUint128(SD1x18 x) pure returns (uint128 result) {
              int64 xInt = SD1x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD1x18_ToUint128_Underflow(x);
              }
              result = uint128(uint64(xInt));
          }
          /// @notice Casts an SD1x18 number into uint40.
          /// @dev Requirements:
          /// - x must be positive.
          /// - x must be less than or equal to `MAX_UINT40`.
          function intoUint40(SD1x18 x) pure returns (uint40 result) {
              int64 xInt = SD1x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD1x18_ToUint40_Underflow(x);
              }
              if (xInt > int64(uint64(Common.MAX_UINT40))) {
                  revert CastingErrors.PRBMath_SD1x18_ToUint40_Overflow(x);
              }
              result = uint40(uint64(xInt));
          }
          /// @notice Alias for {wrap}.
          function sd1x18(int64 x) pure returns (SD1x18 result) {
              result = SD1x18.wrap(x);
          }
          /// @notice Unwraps an SD1x18 number into int64.
          function unwrap(SD1x18 x) pure returns (int64 result) {
              result = SD1x18.unwrap(x);
          }
          /// @notice Wraps an int64 number into SD1x18.
          function wrap(int64 x) pure returns (SD1x18 result) {
              result = SD1x18.wrap(x);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { SD1x18 } from "./ValueType.sol";
          /// @dev Euler's number as an SD1x18 number.
          SD1x18 constant E = SD1x18.wrap(2_718281828459045235);
          /// @dev The maximum value an SD1x18 number can have.
          int64 constant uMAX_SD1x18 = 9_223372036854775807;
          SD1x18 constant MAX_SD1x18 = SD1x18.wrap(uMAX_SD1x18);
          /// @dev The maximum value an SD1x18 number can have.
          int64 constant uMIN_SD1x18 = -9_223372036854775808;
          SD1x18 constant MIN_SD1x18 = SD1x18.wrap(uMIN_SD1x18);
          /// @dev PI as an SD1x18 number.
          SD1x18 constant PI = SD1x18.wrap(3_141592653589793238);
          /// @dev The unit number, which gives the decimal precision of SD1x18.
          SD1x18 constant UNIT = SD1x18.wrap(1e18);
          int256 constant uUNIT = 1e18;
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { SD1x18 } from "./ValueType.sol";
          /// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in UD2x18.
          error PRBMath_SD1x18_ToUD2x18_Underflow(SD1x18 x);
          /// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in UD60x18.
          error PRBMath_SD1x18_ToUD60x18_Underflow(SD1x18 x);
          /// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint128.
          error PRBMath_SD1x18_ToUint128_Underflow(SD1x18 x);
          /// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint256.
          error PRBMath_SD1x18_ToUint256_Underflow(SD1x18 x);
          /// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint40.
          error PRBMath_SD1x18_ToUint40_Overflow(SD1x18 x);
          /// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint40.
          error PRBMath_SD1x18_ToUint40_Underflow(SD1x18 x);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "./Casting.sol" as Casting;
          /// @notice The signed 1.18-decimal fixed-point number representation, which can have up to 1 digit and up to 18
          /// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
          /// type int64. This is useful when end users want to use int64 to save gas, e.g. with tight variable packing in contract
          /// storage.
          type SD1x18 is int64;
          /*//////////////////////////////////////////////////////////////////////////
                                              CASTING
          //////////////////////////////////////////////////////////////////////////*/
          using {
              Casting.intoSD59x18,
              Casting.intoUD2x18,
              Casting.intoUD60x18,
              Casting.intoUint256,
              Casting.intoUint128,
              Casting.intoUint40,
              Casting.unwrap
          } for SD1x18 global;
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "./Errors.sol" as CastingErrors;
          import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
          import { uMAX_SD1x18, uMIN_SD1x18 } from "../sd1x18/Constants.sol";
          import { SD1x18 } from "../sd1x18/ValueType.sol";
          import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
          import { UD2x18 } from "../ud2x18/ValueType.sol";
          import { UD60x18 } from "../ud60x18/ValueType.sol";
          import { SD59x18 } from "./ValueType.sol";
          /// @notice Casts an SD59x18 number into int256.
          /// @dev This is basically a functional alias for {unwrap}.
          function intoInt256(SD59x18 x) pure returns (int256 result) {
              result = SD59x18.unwrap(x);
          }
          /// @notice Casts an SD59x18 number into SD1x18.
          /// @dev Requirements:
          /// - x must be greater than or equal to `uMIN_SD1x18`.
          /// - x must be less than or equal to `uMAX_SD1x18`.
          function intoSD1x18(SD59x18 x) pure returns (SD1x18 result) {
              int256 xInt = SD59x18.unwrap(x);
              if (xInt < uMIN_SD1x18) {
                  revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Underflow(x);
              }
              if (xInt > uMAX_SD1x18) {
                  revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Overflow(x);
              }
              result = SD1x18.wrap(int64(xInt));
          }
          /// @notice Casts an SD59x18 number into UD2x18.
          /// @dev Requirements:
          /// - x must be positive.
          /// - x must be less than or equal to `uMAX_UD2x18`.
          function intoUD2x18(SD59x18 x) pure returns (UD2x18 result) {
              int256 xInt = SD59x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Underflow(x);
              }
              if (xInt > int256(uint256(uMAX_UD2x18))) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Overflow(x);
              }
              result = UD2x18.wrap(uint64(uint256(xInt)));
          }
          /// @notice Casts an SD59x18 number into UD60x18.
          /// @dev Requirements:
          /// - x must be positive.
          function intoUD60x18(SD59x18 x) pure returns (UD60x18 result) {
              int256 xInt = SD59x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUD60x18_Underflow(x);
              }
              result = UD60x18.wrap(uint256(xInt));
          }
          /// @notice Casts an SD59x18 number into uint256.
          /// @dev Requirements:
          /// - x must be positive.
          function intoUint256(SD59x18 x) pure returns (uint256 result) {
              int256 xInt = SD59x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUint256_Underflow(x);
              }
              result = uint256(xInt);
          }
          /// @notice Casts an SD59x18 number into uint128.
          /// @dev Requirements:
          /// - x must be positive.
          /// - x must be less than or equal to `uMAX_UINT128`.
          function intoUint128(SD59x18 x) pure returns (uint128 result) {
              int256 xInt = SD59x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUint128_Underflow(x);
              }
              if (xInt > int256(uint256(MAX_UINT128))) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUint128_Overflow(x);
              }
              result = uint128(uint256(xInt));
          }
          /// @notice Casts an SD59x18 number into uint40.
          /// @dev Requirements:
          /// - x must be positive.
          /// - x must be less than or equal to `MAX_UINT40`.
          function intoUint40(SD59x18 x) pure returns (uint40 result) {
              int256 xInt = SD59x18.unwrap(x);
              if (xInt < 0) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUint40_Underflow(x);
              }
              if (xInt > int256(uint256(MAX_UINT40))) {
                  revert CastingErrors.PRBMath_SD59x18_IntoUint40_Overflow(x);
              }
              result = uint40(uint256(xInt));
          }
          /// @notice Alias for {wrap}.
          function sd(int256 x) pure returns (SD59x18 result) {
              result = SD59x18.wrap(x);
          }
          /// @notice Alias for {wrap}.
          function sd59x18(int256 x) pure returns (SD59x18 result) {
              result = SD59x18.wrap(x);
          }
          /// @notice Unwraps an SD59x18 number into int256.
          function unwrap(SD59x18 x) pure returns (int256 result) {
              result = SD59x18.unwrap(x);
          }
          /// @notice Wraps an int256 number into SD59x18.
          function wrap(int256 x) pure returns (SD59x18 result) {
              result = SD59x18.wrap(x);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { SD59x18 } from "./ValueType.sol";
          // NOTICE: the "u" prefix stands for "unwrapped".
          /// @dev Euler's number as an SD59x18 number.
          SD59x18 constant E = SD59x18.wrap(2_718281828459045235);
          /// @dev The maximum input permitted in {exp}.
          int256 constant uEXP_MAX_INPUT = 133_084258667509499440;
          SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT);
          /// @dev The maximum input permitted in {exp2}.
          int256 constant uEXP2_MAX_INPUT = 192e18 - 1;
          SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT);
          /// @dev Half the UNIT number.
          int256 constant uHALF_UNIT = 0.5e18;
          SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT);
          /// @dev $log_2(10)$ as an SD59x18 number.
          int256 constant uLOG2_10 = 3_321928094887362347;
          SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10);
          /// @dev $log_2(e)$ as an SD59x18 number.
          int256 constant uLOG2_E = 1_442695040888963407;
          SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E);
          /// @dev The maximum value an SD59x18 number can have.
          int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967;
          SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18);
          /// @dev The maximum whole value an SD59x18 number can have.
          int256 constant uMAX_WHOLE_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_000000000000000000;
          SD59x18 constant MAX_WHOLE_SD59x18 = SD59x18.wrap(uMAX_WHOLE_SD59x18);
          /// @dev The minimum value an SD59x18 number can have.
          int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968;
          SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18);
          /// @dev The minimum whole value an SD59x18 number can have.
          int256 constant uMIN_WHOLE_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_000000000000000000;
          SD59x18 constant MIN_WHOLE_SD59x18 = SD59x18.wrap(uMIN_WHOLE_SD59x18);
          /// @dev PI as an SD59x18 number.
          SD59x18 constant PI = SD59x18.wrap(3_141592653589793238);
          /// @dev The unit number, which gives the decimal precision of SD59x18.
          int256 constant uUNIT = 1e18;
          SD59x18 constant UNIT = SD59x18.wrap(1e18);
          /// @dev The unit number squared.
          int256 constant uUNIT_SQUARED = 1e36;
          SD59x18 constant UNIT_SQUARED = SD59x18.wrap(uUNIT_SQUARED);
          /// @dev Zero as an SD59x18 number.
          SD59x18 constant ZERO = SD59x18.wrap(0);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { SD59x18 } from "./ValueType.sol";
          /// @notice Thrown when taking the absolute value of `MIN_SD59x18`.
          error PRBMath_SD59x18_Abs_MinSD59x18();
          /// @notice Thrown when ceiling a number overflows SD59x18.
          error PRBMath_SD59x18_Ceil_Overflow(SD59x18 x);
          /// @notice Thrown when converting a basic integer to the fixed-point format overflows SD59x18.
          error PRBMath_SD59x18_Convert_Overflow(int256 x);
          /// @notice Thrown when converting a basic integer to the fixed-point format underflows SD59x18.
          error PRBMath_SD59x18_Convert_Underflow(int256 x);
          /// @notice Thrown when dividing two numbers and one of them is `MIN_SD59x18`.
          error PRBMath_SD59x18_Div_InputTooSmall();
          /// @notice Thrown when dividing two numbers and one of the intermediary unsigned results overflows SD59x18.
          error PRBMath_SD59x18_Div_Overflow(SD59x18 x, SD59x18 y);
          /// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
          error PRBMath_SD59x18_Exp_InputTooBig(SD59x18 x);
          /// @notice Thrown when taking the binary exponent of a base greater than 192e18.
          error PRBMath_SD59x18_Exp2_InputTooBig(SD59x18 x);
          /// @notice Thrown when flooring a number underflows SD59x18.
          error PRBMath_SD59x18_Floor_Underflow(SD59x18 x);
          /// @notice Thrown when taking the geometric mean of two numbers and their product is negative.
          error PRBMath_SD59x18_Gm_NegativeProduct(SD59x18 x, SD59x18 y);
          /// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows SD59x18.
          error PRBMath_SD59x18_Gm_Overflow(SD59x18 x, SD59x18 y);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
          error PRBMath_SD59x18_IntoSD1x18_Overflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
          error PRBMath_SD59x18_IntoSD1x18_Underflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
          error PRBMath_SD59x18_IntoUD2x18_Overflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
          error PRBMath_SD59x18_IntoUD2x18_Underflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD60x18.
          error PRBMath_SD59x18_IntoUD60x18_Underflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
          error PRBMath_SD59x18_IntoUint128_Overflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
          error PRBMath_SD59x18_IntoUint128_Underflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint256.
          error PRBMath_SD59x18_IntoUint256_Underflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
          error PRBMath_SD59x18_IntoUint40_Overflow(SD59x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
          error PRBMath_SD59x18_IntoUint40_Underflow(SD59x18 x);
          /// @notice Thrown when taking the logarithm of a number less than or equal to zero.
          error PRBMath_SD59x18_Log_InputTooSmall(SD59x18 x);
          /// @notice Thrown when multiplying two numbers and one of the inputs is `MIN_SD59x18`.
          error PRBMath_SD59x18_Mul_InputTooSmall();
          /// @notice Thrown when multiplying two numbers and the intermediary absolute result overflows SD59x18.
          error PRBMath_SD59x18_Mul_Overflow(SD59x18 x, SD59x18 y);
          /// @notice Thrown when raising a number to a power and the intermediary absolute result overflows SD59x18.
          error PRBMath_SD59x18_Powu_Overflow(SD59x18 x, uint256 y);
          /// @notice Thrown when taking the square root of a negative number.
          error PRBMath_SD59x18_Sqrt_NegativeInput(SD59x18 x);
          /// @notice Thrown when the calculating the square root overflows SD59x18.
          error PRBMath_SD59x18_Sqrt_Overflow(SD59x18 x);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { wrap } from "./Casting.sol";
          import { SD59x18 } from "./ValueType.sol";
          /// @notice Implements the checked addition operation (+) in the SD59x18 type.
          function add(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              return wrap(x.unwrap() + y.unwrap());
          }
          /// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
          function and(SD59x18 x, int256 bits) pure returns (SD59x18 result) {
              return wrap(x.unwrap() & bits);
          }
          /// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
          function and2(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              return wrap(x.unwrap() & y.unwrap());
          }
          /// @notice Implements the equal (=) operation in the SD59x18 type.
          function eq(SD59x18 x, SD59x18 y) pure returns (bool result) {
              result = x.unwrap() == y.unwrap();
          }
          /// @notice Implements the greater than operation (>) in the SD59x18 type.
          function gt(SD59x18 x, SD59x18 y) pure returns (bool result) {
              result = x.unwrap() > y.unwrap();
          }
          /// @notice Implements the greater than or equal to operation (>=) in the SD59x18 type.
          function gte(SD59x18 x, SD59x18 y) pure returns (bool result) {
              result = x.unwrap() >= y.unwrap();
          }
          /// @notice Implements a zero comparison check function in the SD59x18 type.
          function isZero(SD59x18 x) pure returns (bool result) {
              result = x.unwrap() == 0;
          }
          /// @notice Implements the left shift operation (<<) in the SD59x18 type.
          function lshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() << bits);
          }
          /// @notice Implements the lower than operation (<) in the SD59x18 type.
          function lt(SD59x18 x, SD59x18 y) pure returns (bool result) {
              result = x.unwrap() < y.unwrap();
          }
          /// @notice Implements the lower than or equal to operation (<=) in the SD59x18 type.
          function lte(SD59x18 x, SD59x18 y) pure returns (bool result) {
              result = x.unwrap() <= y.unwrap();
          }
          /// @notice Implements the unchecked modulo operation (%) in the SD59x18 type.
          function mod(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() % y.unwrap());
          }
          /// @notice Implements the not equal operation (!=) in the SD59x18 type.
          function neq(SD59x18 x, SD59x18 y) pure returns (bool result) {
              result = x.unwrap() != y.unwrap();
          }
          /// @notice Implements the NOT (~) bitwise operation in the SD59x18 type.
          function not(SD59x18 x) pure returns (SD59x18 result) {
              result = wrap(~x.unwrap());
          }
          /// @notice Implements the OR (|) bitwise operation in the SD59x18 type.
          function or(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() | y.unwrap());
          }
          /// @notice Implements the right shift operation (>>) in the SD59x18 type.
          function rshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() >> bits);
          }
          /// @notice Implements the checked subtraction operation (-) in the SD59x18 type.
          function sub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() - y.unwrap());
          }
          /// @notice Implements the checked unary minus operation (-) in the SD59x18 type.
          function unary(SD59x18 x) pure returns (SD59x18 result) {
              result = wrap(-x.unwrap());
          }
          /// @notice Implements the unchecked addition operation (+) in the SD59x18 type.
          function uncheckedAdd(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              unchecked {
                  result = wrap(x.unwrap() + y.unwrap());
              }
          }
          /// @notice Implements the unchecked subtraction operation (-) in the SD59x18 type.
          function uncheckedSub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              unchecked {
                  result = wrap(x.unwrap() - y.unwrap());
              }
          }
          /// @notice Implements the unchecked unary minus operation (-) in the SD59x18 type.
          function uncheckedUnary(SD59x18 x) pure returns (SD59x18 result) {
              unchecked {
                  result = wrap(-x.unwrap());
              }
          }
          /// @notice Implements the XOR (^) bitwise operation in the SD59x18 type.
          function xor(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() ^ y.unwrap());
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "../Common.sol" as Common;
          import "./Errors.sol" as Errors;
          import {
              uEXP_MAX_INPUT,
              uEXP2_MAX_INPUT,
              uHALF_UNIT,
              uLOG2_10,
              uLOG2_E,
              uMAX_SD59x18,
              uMAX_WHOLE_SD59x18,
              uMIN_SD59x18,
              uMIN_WHOLE_SD59x18,
              UNIT,
              uUNIT,
              uUNIT_SQUARED,
              ZERO
          } from "./Constants.sol";
          import { wrap } from "./Helpers.sol";
          import { SD59x18 } from "./ValueType.sol";
          /// @notice Calculates the absolute value of x.
          ///
          /// @dev Requirements:
          /// - x must be greater than `MIN_SD59x18`.
          ///
          /// @param x The SD59x18 number for which to calculate the absolute value.
          /// @param result The absolute value of x as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function abs(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt == uMIN_SD59x18) {
                  revert Errors.PRBMath_SD59x18_Abs_MinSD59x18();
              }
              result = xInt < 0 ? wrap(-xInt) : x;
          }
          /// @notice Calculates the arithmetic average of x and y.
          ///
          /// @dev Notes:
          /// - The result is rounded toward zero.
          ///
          /// @param x The first operand as an SD59x18 number.
          /// @param y The second operand as an SD59x18 number.
          /// @return result The arithmetic average as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function avg(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              int256 yInt = y.unwrap();
              unchecked {
                  // This operation is equivalent to `x / 2 +  y / 2`, and it can never overflow.
                  int256 sum = (xInt >> 1) + (yInt >> 1);
                  if (sum < 0) {
                      // If at least one of x and y is odd, add 1 to the result, because shifting negative numbers to the right
                      // rounds toward negative infinity. The right part is equivalent to `sum + (x % 2 == 1 || y % 2 == 1)`.
                      assembly ("memory-safe") {
                          result := add(sum, and(or(xInt, yInt), 1))
                      }
                  } else {
                      // Add 1 if both x and y are odd to account for the double 0.5 remainder truncated after shifting.
                      result = wrap(sum + (xInt & yInt & 1));
                  }
              }
          }
          /// @notice Yields the smallest whole number greater than or equal to x.
          ///
          /// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
          /// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
          ///
          /// Requirements:
          /// - x must be less than or equal to `MAX_WHOLE_SD59x18`.
          ///
          /// @param x The SD59x18 number to ceil.
          /// @param result The smallest whole number greater than or equal to x, as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function ceil(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt > uMAX_WHOLE_SD59x18) {
                  revert Errors.PRBMath_SD59x18_Ceil_Overflow(x);
              }
              int256 remainder = xInt % uUNIT;
              if (remainder == 0) {
                  result = x;
              } else {
                  unchecked {
                      // Solidity uses C fmod style, which returns a modulus with the same sign as x.
                      int256 resultInt = xInt - remainder;
                      if (xInt > 0) {
                          resultInt += uUNIT;
                      }
                      result = wrap(resultInt);
                  }
              }
          }
          /// @notice Divides two SD59x18 numbers, returning a new SD59x18 number.
          ///
          /// @dev This is an extension of {Common.mulDiv} for signed numbers, which works by computing the signs and the absolute
          /// values separately.
          ///
          /// Notes:
          /// - Refer to the notes in {Common.mulDiv}.
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - Refer to the requirements in {Common.mulDiv}.
          /// - None of the inputs can be `MIN_SD59x18`.
          /// - The denominator must not be zero.
          /// - The result must fit in SD59x18.
          ///
          /// @param x The numerator as an SD59x18 number.
          /// @param y The denominator as an SD59x18 number.
          /// @param result The quotient as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function div(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              int256 yInt = y.unwrap();
              if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
                  revert Errors.PRBMath_SD59x18_Div_InputTooSmall();
              }
              // Get hold of the absolute values of x and y.
              uint256 xAbs;
              uint256 yAbs;
              unchecked {
                  xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
                  yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
              }
              // Compute the absolute value (x*UNIT÷y). The resulting value must fit in SD59x18.
              uint256 resultAbs = Common.mulDiv(xAbs, uint256(uUNIT), yAbs);
              if (resultAbs > uint256(uMAX_SD59x18)) {
                  revert Errors.PRBMath_SD59x18_Div_Overflow(x, y);
              }
              // Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
              // negative, 0 for positive or zero).
              bool sameSign = (xInt ^ yInt) > -1;
              // If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
              unchecked {
                  result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
              }
          }
          /// @notice Calculates the natural exponent of x using the following formula:
          ///
          /// $$
          /// e^x = 2^{x * log_2{e}}
          /// $$
          ///
          /// @dev Notes:
          /// - Refer to the notes in {exp2}.
          ///
          /// Requirements:
          /// - Refer to the requirements in {exp2}.
          /// - x must be less than 133_084258667509499441.
          ///
          /// @param x The exponent as an SD59x18 number.
          /// @return result The result as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function exp(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              // This check prevents values greater than 192e18 from being passed to {exp2}.
              if (xInt > uEXP_MAX_INPUT) {
                  revert Errors.PRBMath_SD59x18_Exp_InputTooBig(x);
              }
              unchecked {
                  // Inline the fixed-point multiplication to save gas.
                  int256 doubleUnitProduct = xInt * uLOG2_E;
                  result = exp2(wrap(doubleUnitProduct / uUNIT));
              }
          }
          /// @notice Calculates the binary exponent of x using the binary fraction method using the following formula:
          ///
          /// $$
          /// 2^{-x} = \\frac{1}{2^x}
          /// $$
          ///
          /// @dev See https://ethereum.stackexchange.com/q/79903/24693.
          ///
          /// Notes:
          /// - If x is less than -59_794705707972522261, the result is zero.
          ///
          /// Requirements:
          /// - x must be less than 192e18.
          /// - The result must fit in SD59x18.
          ///
          /// @param x The exponent as an SD59x18 number.
          /// @return result The result as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function exp2(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt < 0) {
                  // The inverse of any number less than this is truncated to zero.
                  if (xInt < -59_794705707972522261) {
                      return ZERO;
                  }
                  unchecked {
                      // Inline the fixed-point inversion to save gas.
                      result = wrap(uUNIT_SQUARED / exp2(wrap(-xInt)).unwrap());
                  }
              } else {
                  // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
                  if (xInt > uEXP2_MAX_INPUT) {
                      revert Errors.PRBMath_SD59x18_Exp2_InputTooBig(x);
                  }
                  unchecked {
                      // Convert x to the 192.64-bit fixed-point format.
                      uint256 x_192x64 = uint256((xInt << 64) / uUNIT);
                      // It is safe to cast the result to int256 due to the checks above.
                      result = wrap(int256(Common.exp2(x_192x64)));
                  }
              }
          }
          /// @notice Yields the greatest whole number less than or equal to x.
          ///
          /// @dev Optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
          /// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
          ///
          /// Requirements:
          /// - x must be greater than or equal to `MIN_WHOLE_SD59x18`.
          ///
          /// @param x The SD59x18 number to floor.
          /// @param result The greatest whole number less than or equal to x, as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function floor(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt < uMIN_WHOLE_SD59x18) {
                  revert Errors.PRBMath_SD59x18_Floor_Underflow(x);
              }
              int256 remainder = xInt % uUNIT;
              if (remainder == 0) {
                  result = x;
              } else {
                  unchecked {
                      // Solidity uses C fmod style, which returns a modulus with the same sign as x.
                      int256 resultInt = xInt - remainder;
                      if (xInt < 0) {
                          resultInt -= uUNIT;
                      }
                      result = wrap(resultInt);
                  }
              }
          }
          /// @notice Yields the excess beyond the floor of x for positive numbers and the part of the number to the right.
          /// of the radix point for negative numbers.
          /// @dev Based on the odd function definition. https://en.wikipedia.org/wiki/Fractional_part
          /// @param x The SD59x18 number to get the fractional part of.
          /// @param result The fractional part of x as an SD59x18 number.
          function frac(SD59x18 x) pure returns (SD59x18 result) {
              result = wrap(x.unwrap() % uUNIT);
          }
          /// @notice Calculates the geometric mean of x and y, i.e. $\\sqrt{x * y}$.
          ///
          /// @dev Notes:
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - x * y must fit in SD59x18.
          /// - x * y must not be negative, since complex numbers are not supported.
          ///
          /// @param x The first operand as an SD59x18 number.
          /// @param y The second operand as an SD59x18 number.
          /// @return result The result as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function gm(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              int256 yInt = y.unwrap();
              if (xInt == 0 || yInt == 0) {
                  return ZERO;
              }
              unchecked {
                  // Equivalent to `xy / x != y`. Checking for overflow this way is faster than letting Solidity do it.
                  int256 xyInt = xInt * yInt;
                  if (xyInt / xInt != yInt) {
                      revert Errors.PRBMath_SD59x18_Gm_Overflow(x, y);
                  }
                  // The product must not be negative, since complex numbers are not supported.
                  if (xyInt < 0) {
                      revert Errors.PRBMath_SD59x18_Gm_NegativeProduct(x, y);
                  }
                  // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
                  // during multiplication. See the comments in {Common.sqrt}.
                  uint256 resultUint = Common.sqrt(uint256(xyInt));
                  result = wrap(int256(resultUint));
              }
          }
          /// @notice Calculates the inverse of x.
          ///
          /// @dev Notes:
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - x must not be zero.
          ///
          /// @param x The SD59x18 number for which to calculate the inverse.
          /// @return result The inverse as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function inv(SD59x18 x) pure returns (SD59x18 result) {
              result = wrap(uUNIT_SQUARED / x.unwrap());
          }
          /// @notice Calculates the natural logarithm of x using the following formula:
          ///
          /// $$
          /// ln{x} = log_2{x} / log_2{e}
          /// $$
          ///
          /// @dev Notes:
          /// - Refer to the notes in {log2}.
          /// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
          ///
          /// Requirements:
          /// - Refer to the requirements in {log2}.
          ///
          /// @param x The SD59x18 number for which to calculate the natural logarithm.
          /// @return result The natural logarithm as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function ln(SD59x18 x) pure returns (SD59x18 result) {
              // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
              // {log2} can return is ~195_205294292027477728.
              result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
          }
          /// @notice Calculates the common logarithm of x using the following formula:
          ///
          /// $$
          /// log_{10}{x} = log_2{x} / log_2{10}
          /// $$
          ///
          /// However, if x is an exact power of ten, a hard coded value is returned.
          ///
          /// @dev Notes:
          /// - Refer to the notes in {log2}.
          ///
          /// Requirements:
          /// - Refer to the requirements in {log2}.
          ///
          /// @param x The SD59x18 number for which to calculate the common logarithm.
          /// @return result The common logarithm as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function log10(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt < 0) {
                  revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
              }
              // Note that the `mul` in this block is the standard multiplication operation, not {SD59x18.mul}.
              // prettier-ignore
              assembly ("memory-safe") {
                  switch x
                  case 1 { result := mul(uUNIT, sub(0, 18)) }
                  case 10 { result := mul(uUNIT, sub(1, 18)) }
                  case 100 { result := mul(uUNIT, sub(2, 18)) }
                  case 1000 { result := mul(uUNIT, sub(3, 18)) }
                  case 10000 { result := mul(uUNIT, sub(4, 18)) }
                  case 100000 { result := mul(uUNIT, sub(5, 18)) }
                  case 1000000 { result := mul(uUNIT, sub(6, 18)) }
                  case 10000000 { result := mul(uUNIT, sub(7, 18)) }
                  case 100000000 { result := mul(uUNIT, sub(8, 18)) }
                  case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
                  case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
                  case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
                  case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
                  case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
                  case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
                  case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
                  case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
                  case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
                  case 1000000000000000000 { result := 0 }
                  case 10000000000000000000 { result := uUNIT }
                  case 100000000000000000000 { result := mul(uUNIT, 2) }
                  case 1000000000000000000000 { result := mul(uUNIT, 3) }
                  case 10000000000000000000000 { result := mul(uUNIT, 4) }
                  case 100000000000000000000000 { result := mul(uUNIT, 5) }
                  case 1000000000000000000000000 { result := mul(uUNIT, 6) }
                  case 10000000000000000000000000 { result := mul(uUNIT, 7) }
                  case 100000000000000000000000000 { result := mul(uUNIT, 8) }
                  case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
                  case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
                  case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
                  case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
                  case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
                  case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
                  case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
                  case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
                  case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
                  case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
                  case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
                  case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
                  case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
                  case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
                  case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
                  case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
                  case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
                  case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
                  case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
                  case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
                  case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
                  case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
                  case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
                  case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
                  case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
                  case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
                  case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
                  case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
                  case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
                  case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
                  case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
                  case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
                  case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
                  case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
                  case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
                  case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
                  case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
                  case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
                  case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
                  default { result := uMAX_SD59x18 }
              }
              if (result.unwrap() == uMAX_SD59x18) {
                  unchecked {
                      // Inline the fixed-point division to save gas.
                      result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
                  }
              }
          }
          /// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
          ///
          /// $$
          /// log_2{x} = n + log_2{y}, \\text{ where } y = x*2^{-n}, \\ y \\in [1, 2)
          /// $$
          ///
          /// For $0 \\leq x \\lt 1$, the input is inverted:
          ///
          /// $$
          /// log_2{x} = -log_2{\\frac{1}{x}}
          /// $$
          ///
          /// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation.
          ///
          /// Notes:
          /// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
          ///
          /// Requirements:
          /// - x must be greater than zero.
          ///
          /// @param x The SD59x18 number for which to calculate the binary logarithm.
          /// @return result The binary logarithm as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function log2(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt <= 0) {
                  revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
              }
              unchecked {
                  int256 sign;
                  if (xInt >= uUNIT) {
                      sign = 1;
                  } else {
                      sign = -1;
                      // Inline the fixed-point inversion to save gas.
                      xInt = uUNIT_SQUARED / xInt;
                  }
                  // Calculate the integer part of the logarithm.
                  uint256 n = Common.msb(uint256(xInt / uUNIT));
                  // This is the integer part of the logarithm as an SD59x18 number. The operation can't overflow
                  // because n is at most 255, `UNIT` is 1e18, and the sign is either 1 or -1.
                  int256 resultInt = int256(n) * uUNIT;
                  // Calculate $y = x * 2^{-n}$.
                  int256 y = xInt >> n;
                  // If y is the unit number, the fractional part is zero.
                  if (y == uUNIT) {
                      return wrap(resultInt * sign);
                  }
                  // Calculate the fractional part via the iterative approximation.
                  // The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
                  int256 DOUBLE_UNIT = 2e18;
                  for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
                      y = (y * y) / uUNIT;
                      // Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
                      if (y >= DOUBLE_UNIT) {
                          // Add the 2^{-m} factor to the logarithm.
                          resultInt = resultInt + delta;
                          // Halve y, which corresponds to z/2 in the Wikipedia article.
                          y >>= 1;
                      }
                  }
                  resultInt *= sign;
                  result = wrap(resultInt);
              }
          }
          /// @notice Multiplies two SD59x18 numbers together, returning a new SD59x18 number.
          ///
          /// @dev Notes:
          /// - Refer to the notes in {Common.mulDiv18}.
          ///
          /// Requirements:
          /// - Refer to the requirements in {Common.mulDiv18}.
          /// - None of the inputs can be `MIN_SD59x18`.
          /// - The result must fit in SD59x18.
          ///
          /// @param x The multiplicand as an SD59x18 number.
          /// @param y The multiplier as an SD59x18 number.
          /// @return result The product as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function mul(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              int256 yInt = y.unwrap();
              if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
                  revert Errors.PRBMath_SD59x18_Mul_InputTooSmall();
              }
              // Get hold of the absolute values of x and y.
              uint256 xAbs;
              uint256 yAbs;
              unchecked {
                  xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
                  yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
              }
              // Compute the absolute value (x*y÷UNIT). The resulting value must fit in SD59x18.
              uint256 resultAbs = Common.mulDiv18(xAbs, yAbs);
              if (resultAbs > uint256(uMAX_SD59x18)) {
                  revert Errors.PRBMath_SD59x18_Mul_Overflow(x, y);
              }
              // Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
              // negative, 0 for positive or zero).
              bool sameSign = (xInt ^ yInt) > -1;
              // If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
              unchecked {
                  result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
              }
          }
          /// @notice Raises x to the power of y using the following formula:
          ///
          /// $$
          /// x^y = 2^{log_2{x} * y}
          /// $$
          ///
          /// @dev Notes:
          /// - Refer to the notes in {exp2}, {log2}, and {mul}.
          /// - Returns `UNIT` for 0^0.
          ///
          /// Requirements:
          /// - Refer to the requirements in {exp2}, {log2}, and {mul}.
          ///
          /// @param x The base as an SD59x18 number.
          /// @param y Exponent to raise x to, as an SD59x18 number
          /// @return result x raised to power y, as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function pow(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              int256 yInt = y.unwrap();
              // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
              if (xInt == 0) {
                  return yInt == 0 ? UNIT : ZERO;
              }
              // If x is `UNIT`, the result is always `UNIT`.
              else if (xInt == uUNIT) {
                  return UNIT;
              }
              // If y is zero, the result is always `UNIT`.
              if (yInt == 0) {
                  return UNIT;
              }
              // If y is `UNIT`, the result is always x.
              else if (yInt == uUNIT) {
                  return x;
              }
              // Calculate the result using the formula.
              result = exp2(mul(log2(x), y));
          }
          /// @notice Raises x (an SD59x18 number) to the power y (an unsigned basic integer) using the well-known
          /// algorithm "exponentiation by squaring".
          ///
          /// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
          ///
          /// Notes:
          /// - Refer to the notes in {Common.mulDiv18}.
          /// - Returns `UNIT` for 0^0.
          ///
          /// Requirements:
          /// - Refer to the requirements in {abs} and {Common.mulDiv18}.
          /// - The result must fit in SD59x18.
          ///
          /// @param x The base as an SD59x18 number.
          /// @param y The exponent as a uint256.
          /// @return result The result as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function powu(SD59x18 x, uint256 y) pure returns (SD59x18 result) {
              uint256 xAbs = uint256(abs(x).unwrap());
              // Calculate the first iteration of the loop in advance.
              uint256 resultAbs = y & 1 > 0 ? xAbs : uint256(uUNIT);
              // Equivalent to `for(y /= 2; y > 0; y /= 2)`.
              uint256 yAux = y;
              for (yAux >>= 1; yAux > 0; yAux >>= 1) {
                  xAbs = Common.mulDiv18(xAbs, xAbs);
                  // Equivalent to `y % 2 == 1`.
                  if (yAux & 1 > 0) {
                      resultAbs = Common.mulDiv18(resultAbs, xAbs);
                  }
              }
              // The result must fit in SD59x18.
              if (resultAbs > uint256(uMAX_SD59x18)) {
                  revert Errors.PRBMath_SD59x18_Powu_Overflow(x, y);
              }
              unchecked {
                  // Is the base negative and the exponent odd? If yes, the result should be negative.
                  int256 resultInt = int256(resultAbs);
                  bool isNegative = x.unwrap() < 0 && y & 1 == 1;
                  if (isNegative) {
                      resultInt = -resultInt;
                  }
                  result = wrap(resultInt);
              }
          }
          /// @notice Calculates the square root of x using the Babylonian method.
          ///
          /// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
          ///
          /// Notes:
          /// - Only the positive root is returned.
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - x cannot be negative, since complex numbers are not supported.
          /// - x must be less than `MAX_SD59x18 / UNIT`.
          ///
          /// @param x The SD59x18 number for which to calculate the square root.
          /// @return result The result as an SD59x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function sqrt(SD59x18 x) pure returns (SD59x18 result) {
              int256 xInt = x.unwrap();
              if (xInt < 0) {
                  revert Errors.PRBMath_SD59x18_Sqrt_NegativeInput(x);
              }
              if (xInt > uMAX_SD59x18 / uUNIT) {
                  revert Errors.PRBMath_SD59x18_Sqrt_Overflow(x);
              }
              unchecked {
                  // Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two SD59x18 numbers.
                  // In this case, the two numbers are both the square root.
                  uint256 resultUint = Common.sqrt(uint256(xInt * uUNIT));
                  result = wrap(int256(resultUint));
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "./Casting.sol" as Casting;
          import "./Helpers.sol" as Helpers;
          import "./Math.sol" as Math;
          /// @notice The signed 59.18-decimal fixed-point number representation, which can have up to 59 digits and up to 18
          /// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
          /// type int256.
          type SD59x18 is int256;
          /*//////////////////////////////////////////////////////////////////////////
                                              CASTING
          //////////////////////////////////////////////////////////////////////////*/
          using {
              Casting.intoInt256,
              Casting.intoSD1x18,
              Casting.intoUD2x18,
              Casting.intoUD60x18,
              Casting.intoUint256,
              Casting.intoUint128,
              Casting.intoUint40,
              Casting.unwrap
          } for SD59x18 global;
          /*//////////////////////////////////////////////////////////////////////////
                                      MATHEMATICAL FUNCTIONS
          //////////////////////////////////////////////////////////////////////////*/
          using {
              Math.abs,
              Math.avg,
              Math.ceil,
              Math.div,
              Math.exp,
              Math.exp2,
              Math.floor,
              Math.frac,
              Math.gm,
              Math.inv,
              Math.log10,
              Math.log2,
              Math.ln,
              Math.mul,
              Math.pow,
              Math.powu,
              Math.sqrt
          } for SD59x18 global;
          /*//////////////////////////////////////////////////////////////////////////
                                          HELPER FUNCTIONS
          //////////////////////////////////////////////////////////////////////////*/
          using {
              Helpers.add,
              Helpers.and,
              Helpers.eq,
              Helpers.gt,
              Helpers.gte,
              Helpers.isZero,
              Helpers.lshift,
              Helpers.lt,
              Helpers.lte,
              Helpers.mod,
              Helpers.neq,
              Helpers.not,
              Helpers.or,
              Helpers.rshift,
              Helpers.sub,
              Helpers.uncheckedAdd,
              Helpers.uncheckedSub,
              Helpers.uncheckedUnary,
              Helpers.xor
          } for SD59x18 global;
          /*//////////////////////////////////////////////////////////////////////////
                                              OPERATORS
          //////////////////////////////////////////////////////////////////////////*/
          // The global "using for" directive makes it possible to use these operators on the SD59x18 type.
          using {
              Helpers.add as +,
              Helpers.and2 as &,
              Math.div as /,
              Helpers.eq as ==,
              Helpers.gt as >,
              Helpers.gte as >=,
              Helpers.lt as <,
              Helpers.lte as <=,
              Helpers.mod as %,
              Math.mul as *,
              Helpers.neq as !=,
              Helpers.not as ~,
              Helpers.or as |,
              Helpers.sub as -,
              Helpers.unary as -,
              Helpers.xor as ^
          } for SD59x18 global;
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "../Common.sol" as Common;
          import "./Errors.sol" as Errors;
          import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
          import { SD1x18 } from "../sd1x18/ValueType.sol";
          import { SD59x18 } from "../sd59x18/ValueType.sol";
          import { UD60x18 } from "../ud60x18/ValueType.sol";
          import { UD2x18 } from "./ValueType.sol";
          /// @notice Casts a UD2x18 number into SD1x18.
          /// - x must be less than or equal to `uMAX_SD1x18`.
          function intoSD1x18(UD2x18 x) pure returns (SD1x18 result) {
              uint64 xUint = UD2x18.unwrap(x);
              if (xUint > uint64(uMAX_SD1x18)) {
                  revert Errors.PRBMath_UD2x18_IntoSD1x18_Overflow(x);
              }
              result = SD1x18.wrap(int64(xUint));
          }
          /// @notice Casts a UD2x18 number into SD59x18.
          /// @dev There is no overflow check because the domain of UD2x18 is a subset of SD59x18.
          function intoSD59x18(UD2x18 x) pure returns (SD59x18 result) {
              result = SD59x18.wrap(int256(uint256(UD2x18.unwrap(x))));
          }
          /// @notice Casts a UD2x18 number into UD60x18.
          /// @dev There is no overflow check because the domain of UD2x18 is a subset of UD60x18.
          function intoUD60x18(UD2x18 x) pure returns (UD60x18 result) {
              result = UD60x18.wrap(UD2x18.unwrap(x));
          }
          /// @notice Casts a UD2x18 number into uint128.
          /// @dev There is no overflow check because the domain of UD2x18 is a subset of uint128.
          function intoUint128(UD2x18 x) pure returns (uint128 result) {
              result = uint128(UD2x18.unwrap(x));
          }
          /// @notice Casts a UD2x18 number into uint256.
          /// @dev There is no overflow check because the domain of UD2x18 is a subset of uint256.
          function intoUint256(UD2x18 x) pure returns (uint256 result) {
              result = uint256(UD2x18.unwrap(x));
          }
          /// @notice Casts a UD2x18 number into uint40.
          /// @dev Requirements:
          /// - x must be less than or equal to `MAX_UINT40`.
          function intoUint40(UD2x18 x) pure returns (uint40 result) {
              uint64 xUint = UD2x18.unwrap(x);
              if (xUint > uint64(Common.MAX_UINT40)) {
                  revert Errors.PRBMath_UD2x18_IntoUint40_Overflow(x);
              }
              result = uint40(xUint);
          }
          /// @notice Alias for {wrap}.
          function ud2x18(uint64 x) pure returns (UD2x18 result) {
              result = UD2x18.wrap(x);
          }
          /// @notice Unwrap a UD2x18 number into uint64.
          function unwrap(UD2x18 x) pure returns (uint64 result) {
              result = UD2x18.unwrap(x);
          }
          /// @notice Wraps a uint64 number into UD2x18.
          function wrap(uint64 x) pure returns (UD2x18 result) {
              result = UD2x18.wrap(x);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { UD2x18 } from "./ValueType.sol";
          /// @dev Euler's number as a UD2x18 number.
          UD2x18 constant E = UD2x18.wrap(2_718281828459045235);
          /// @dev The maximum value a UD2x18 number can have.
          uint64 constant uMAX_UD2x18 = 18_446744073709551615;
          UD2x18 constant MAX_UD2x18 = UD2x18.wrap(uMAX_UD2x18);
          /// @dev PI as a UD2x18 number.
          UD2x18 constant PI = UD2x18.wrap(3_141592653589793238);
          /// @dev The unit number, which gives the decimal precision of UD2x18.
          uint256 constant uUNIT = 1e18;
          UD2x18 constant UNIT = UD2x18.wrap(1e18);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { UD2x18 } from "./ValueType.sol";
          /// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in SD1x18.
          error PRBMath_UD2x18_IntoSD1x18_Overflow(UD2x18 x);
          /// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in uint40.
          error PRBMath_UD2x18_IntoUint40_Overflow(UD2x18 x);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "./Casting.sol" as Casting;
          /// @notice The unsigned 2.18-decimal fixed-point number representation, which can have up to 2 digits and up to 18
          /// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
          /// type uint64. This is useful when end users want to use uint64 to save gas, e.g. with tight variable packing in contract
          /// storage.
          type UD2x18 is uint64;
          /*//////////////////////////////////////////////////////////////////////////
                                              CASTING
          //////////////////////////////////////////////////////////////////////////*/
          using {
              Casting.intoSD1x18,
              Casting.intoSD59x18,
              Casting.intoUD60x18,
              Casting.intoUint256,
              Casting.intoUint128,
              Casting.intoUint40,
              Casting.unwrap
          } for UD2x18 global;
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          /*
          ██████╗ ██████╗ ██████╗ ███╗   ███╗ █████╗ ████████╗██╗  ██╗
          ██╔══██╗██╔══██╗██╔══██╗████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
          ██████╔╝██████╔╝██████╔╝██╔████╔██║███████║   ██║   ███████║
          ██╔═══╝ ██╔══██╗██╔══██╗██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
          ██║     ██║  ██║██████╔╝██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
          ╚═╝     ╚═╝  ╚═╝╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝
          ██╗   ██╗██████╗  ██████╗  ██████╗ ██╗  ██╗ ██╗ █████╗
          ██║   ██║██╔══██╗██╔════╝ ██╔═████╗╚██╗██╔╝███║██╔══██╗
          ██║   ██║██║  ██║███████╗ ██║██╔██║ ╚███╔╝ ╚██║╚█████╔╝
          ██║   ██║██║  ██║██╔═══██╗████╔╝██║ ██╔██╗  ██║██╔══██╗
          ╚██████╔╝██████╔╝╚██████╔╝╚██████╔╝██╔╝ ██╗ ██║╚█████╔╝
           ╚═════╝ ╚═════╝  ╚═════╝  ╚═════╝ ╚═╝  ╚═╝ ╚═╝ ╚════╝
          */
          import "./ud60x18/Casting.sol";
          import "./ud60x18/Constants.sol";
          import "./ud60x18/Conversions.sol";
          import "./ud60x18/Errors.sol";
          import "./ud60x18/Helpers.sol";
          import "./ud60x18/Math.sol";
          import "./ud60x18/ValueType.sol";
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "./Errors.sol" as CastingErrors;
          import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
          import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
          import { SD1x18 } from "../sd1x18/ValueType.sol";
          import { uMAX_SD59x18 } from "../sd59x18/Constants.sol";
          import { SD59x18 } from "../sd59x18/ValueType.sol";
          import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
          import { UD2x18 } from "../ud2x18/ValueType.sol";
          import { UD60x18 } from "./ValueType.sol";
          /// @notice Casts a UD60x18 number into SD1x18.
          /// @dev Requirements:
          /// - x must be less than or equal to `uMAX_SD1x18`.
          function intoSD1x18(UD60x18 x) pure returns (SD1x18 result) {
              uint256 xUint = UD60x18.unwrap(x);
              if (xUint > uint256(int256(uMAX_SD1x18))) {
                  revert CastingErrors.PRBMath_UD60x18_IntoSD1x18_Overflow(x);
              }
              result = SD1x18.wrap(int64(uint64(xUint)));
          }
          /// @notice Casts a UD60x18 number into UD2x18.
          /// @dev Requirements:
          /// - x must be less than or equal to `uMAX_UD2x18`.
          function intoUD2x18(UD60x18 x) pure returns (UD2x18 result) {
              uint256 xUint = UD60x18.unwrap(x);
              if (xUint > uMAX_UD2x18) {
                  revert CastingErrors.PRBMath_UD60x18_IntoUD2x18_Overflow(x);
              }
              result = UD2x18.wrap(uint64(xUint));
          }
          /// @notice Casts a UD60x18 number into SD59x18.
          /// @dev Requirements:
          /// - x must be less than or equal to `uMAX_SD59x18`.
          function intoSD59x18(UD60x18 x) pure returns (SD59x18 result) {
              uint256 xUint = UD60x18.unwrap(x);
              if (xUint > uint256(uMAX_SD59x18)) {
                  revert CastingErrors.PRBMath_UD60x18_IntoSD59x18_Overflow(x);
              }
              result = SD59x18.wrap(int256(xUint));
          }
          /// @notice Casts a UD60x18 number into uint128.
          /// @dev This is basically an alias for {unwrap}.
          function intoUint256(UD60x18 x) pure returns (uint256 result) {
              result = UD60x18.unwrap(x);
          }
          /// @notice Casts a UD60x18 number into uint128.
          /// @dev Requirements:
          /// - x must be less than or equal to `MAX_UINT128`.
          function intoUint128(UD60x18 x) pure returns (uint128 result) {
              uint256 xUint = UD60x18.unwrap(x);
              if (xUint > MAX_UINT128) {
                  revert CastingErrors.PRBMath_UD60x18_IntoUint128_Overflow(x);
              }
              result = uint128(xUint);
          }
          /// @notice Casts a UD60x18 number into uint40.
          /// @dev Requirements:
          /// - x must be less than or equal to `MAX_UINT40`.
          function intoUint40(UD60x18 x) pure returns (uint40 result) {
              uint256 xUint = UD60x18.unwrap(x);
              if (xUint > MAX_UINT40) {
                  revert CastingErrors.PRBMath_UD60x18_IntoUint40_Overflow(x);
              }
              result = uint40(xUint);
          }
          /// @notice Alias for {wrap}.
          function ud(uint256 x) pure returns (UD60x18 result) {
              result = UD60x18.wrap(x);
          }
          /// @notice Alias for {wrap}.
          function ud60x18(uint256 x) pure returns (UD60x18 result) {
              result = UD60x18.wrap(x);
          }
          /// @notice Unwraps a UD60x18 number into uint256.
          function unwrap(UD60x18 x) pure returns (uint256 result) {
              result = UD60x18.unwrap(x);
          }
          /// @notice Wraps a uint256 number into the UD60x18 value type.
          function wrap(uint256 x) pure returns (UD60x18 result) {
              result = UD60x18.wrap(x);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { UD60x18 } from "./ValueType.sol";
          // NOTICE: the "u" prefix stands for "unwrapped".
          /// @dev Euler's number as a UD60x18 number.
          UD60x18 constant E = UD60x18.wrap(2_718281828459045235);
          /// @dev The maximum input permitted in {exp}.
          uint256 constant uEXP_MAX_INPUT = 133_084258667509499440;
          UD60x18 constant EXP_MAX_INPUT = UD60x18.wrap(uEXP_MAX_INPUT);
          /// @dev The maximum input permitted in {exp2}.
          uint256 constant uEXP2_MAX_INPUT = 192e18 - 1;
          UD60x18 constant EXP2_MAX_INPUT = UD60x18.wrap(uEXP2_MAX_INPUT);
          /// @dev Half the UNIT number.
          uint256 constant uHALF_UNIT = 0.5e18;
          UD60x18 constant HALF_UNIT = UD60x18.wrap(uHALF_UNIT);
          /// @dev $log_2(10)$ as a UD60x18 number.
          uint256 constant uLOG2_10 = 3_321928094887362347;
          UD60x18 constant LOG2_10 = UD60x18.wrap(uLOG2_10);
          /// @dev $log_2(e)$ as a UD60x18 number.
          uint256 constant uLOG2_E = 1_442695040888963407;
          UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E);
          /// @dev The maximum value a UD60x18 number can have.
          uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935;
          UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18);
          /// @dev The maximum whole value a UD60x18 number can have.
          uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000;
          UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18);
          /// @dev PI as a UD60x18 number.
          UD60x18 constant PI = UD60x18.wrap(3_141592653589793238);
          /// @dev The unit number, which gives the decimal precision of UD60x18.
          uint256 constant uUNIT = 1e18;
          UD60x18 constant UNIT = UD60x18.wrap(uUNIT);
          /// @dev The unit number squared.
          uint256 constant uUNIT_SQUARED = 1e36;
          UD60x18 constant UNIT_SQUARED = UD60x18.wrap(uUNIT_SQUARED);
          /// @dev Zero as a UD60x18 number.
          UD60x18 constant ZERO = UD60x18.wrap(0);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { uMAX_UD60x18, uUNIT } from "./Constants.sol";
          import { PRBMath_UD60x18_Convert_Overflow } from "./Errors.sol";
          import { UD60x18 } from "./ValueType.sol";
          /// @notice Converts a UD60x18 number to a simple integer by dividing it by `UNIT`.
          /// @dev The result is rounded toward zero.
          /// @param x The UD60x18 number to convert.
          /// @return result The same number in basic integer form.
          function convert(UD60x18 x) pure returns (uint256 result) {
              result = UD60x18.unwrap(x) / uUNIT;
          }
          /// @notice Converts a simple integer to UD60x18 by multiplying it by `UNIT`.
          ///
          /// @dev Requirements:
          /// - x must be less than or equal to `MAX_UD60x18 / UNIT`.
          ///
          /// @param x The basic integer to convert.
          /// @param result The same number converted to UD60x18.
          function convert(uint256 x) pure returns (UD60x18 result) {
              if (x > uMAX_UD60x18 / uUNIT) {
                  revert PRBMath_UD60x18_Convert_Overflow(x);
              }
              unchecked {
                  result = UD60x18.wrap(x * uUNIT);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { UD60x18 } from "./ValueType.sol";
          /// @notice Thrown when ceiling a number overflows UD60x18.
          error PRBMath_UD60x18_Ceil_Overflow(UD60x18 x);
          /// @notice Thrown when converting a basic integer to the fixed-point format overflows UD60x18.
          error PRBMath_UD60x18_Convert_Overflow(uint256 x);
          /// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
          error PRBMath_UD60x18_Exp_InputTooBig(UD60x18 x);
          /// @notice Thrown when taking the binary exponent of a base greater than 192e18.
          error PRBMath_UD60x18_Exp2_InputTooBig(UD60x18 x);
          /// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows UD60x18.
          error PRBMath_UD60x18_Gm_Overflow(UD60x18 x, UD60x18 y);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
          error PRBMath_UD60x18_IntoSD1x18_Overflow(UD60x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD59x18.
          error PRBMath_UD60x18_IntoSD59x18_Overflow(UD60x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
          error PRBMath_UD60x18_IntoUD2x18_Overflow(UD60x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
          error PRBMath_UD60x18_IntoUint128_Overflow(UD60x18 x);
          /// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
          error PRBMath_UD60x18_IntoUint40_Overflow(UD60x18 x);
          /// @notice Thrown when taking the logarithm of a number less than 1.
          error PRBMath_UD60x18_Log_InputTooSmall(UD60x18 x);
          /// @notice Thrown when calculating the square root overflows UD60x18.
          error PRBMath_UD60x18_Sqrt_Overflow(UD60x18 x);
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import { wrap } from "./Casting.sol";
          import { UD60x18 } from "./ValueType.sol";
          /// @notice Implements the checked addition operation (+) in the UD60x18 type.
          function add(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() + y.unwrap());
          }
          /// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
          function and(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() & bits);
          }
          /// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
          function and2(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() & y.unwrap());
          }
          /// @notice Implements the equal operation (==) in the UD60x18 type.
          function eq(UD60x18 x, UD60x18 y) pure returns (bool result) {
              result = x.unwrap() == y.unwrap();
          }
          /// @notice Implements the greater than operation (>) in the UD60x18 type.
          function gt(UD60x18 x, UD60x18 y) pure returns (bool result) {
              result = x.unwrap() > y.unwrap();
          }
          /// @notice Implements the greater than or equal to operation (>=) in the UD60x18 type.
          function gte(UD60x18 x, UD60x18 y) pure returns (bool result) {
              result = x.unwrap() >= y.unwrap();
          }
          /// @notice Implements a zero comparison check function in the UD60x18 type.
          function isZero(UD60x18 x) pure returns (bool result) {
              // This wouldn't work if x could be negative.
              result = x.unwrap() == 0;
          }
          /// @notice Implements the left shift operation (<<) in the UD60x18 type.
          function lshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() << bits);
          }
          /// @notice Implements the lower than operation (<) in the UD60x18 type.
          function lt(UD60x18 x, UD60x18 y) pure returns (bool result) {
              result = x.unwrap() < y.unwrap();
          }
          /// @notice Implements the lower than or equal to operation (<=) in the UD60x18 type.
          function lte(UD60x18 x, UD60x18 y) pure returns (bool result) {
              result = x.unwrap() <= y.unwrap();
          }
          /// @notice Implements the checked modulo operation (%) in the UD60x18 type.
          function mod(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() % y.unwrap());
          }
          /// @notice Implements the not equal operation (!=) in the UD60x18 type.
          function neq(UD60x18 x, UD60x18 y) pure returns (bool result) {
              result = x.unwrap() != y.unwrap();
          }
          /// @notice Implements the NOT (~) bitwise operation in the UD60x18 type.
          function not(UD60x18 x) pure returns (UD60x18 result) {
              result = wrap(~x.unwrap());
          }
          /// @notice Implements the OR (|) bitwise operation in the UD60x18 type.
          function or(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() | y.unwrap());
          }
          /// @notice Implements the right shift operation (>>) in the UD60x18 type.
          function rshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() >> bits);
          }
          /// @notice Implements the checked subtraction operation (-) in the UD60x18 type.
          function sub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() - y.unwrap());
          }
          /// @notice Implements the unchecked addition operation (+) in the UD60x18 type.
          function uncheckedAdd(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              unchecked {
                  result = wrap(x.unwrap() + y.unwrap());
              }
          }
          /// @notice Implements the unchecked subtraction operation (-) in the UD60x18 type.
          function uncheckedSub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              unchecked {
                  result = wrap(x.unwrap() - y.unwrap());
              }
          }
          /// @notice Implements the XOR (^) bitwise operation in the UD60x18 type.
          function xor(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(x.unwrap() ^ y.unwrap());
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "../Common.sol" as Common;
          import "./Errors.sol" as Errors;
          import { wrap } from "./Casting.sol";
          import {
              uEXP_MAX_INPUT,
              uEXP2_MAX_INPUT,
              uHALF_UNIT,
              uLOG2_10,
              uLOG2_E,
              uMAX_UD60x18,
              uMAX_WHOLE_UD60x18,
              UNIT,
              uUNIT,
              uUNIT_SQUARED,
              ZERO
          } from "./Constants.sol";
          import { UD60x18 } from "./ValueType.sol";
          /*//////////////////////////////////////////////////////////////////////////
                                      MATHEMATICAL FUNCTIONS
          //////////////////////////////////////////////////////////////////////////*/
          /// @notice Calculates the arithmetic average of x and y using the following formula:
          ///
          /// $$
          /// avg(x, y) = (x & y) + ((xUint ^ yUint) / 2)
          /// $$
          ///
          /// In English, this is what this formula does:
          ///
          /// 1. AND x and y.
          /// 2. Calculate half of XOR x and y.
          /// 3. Add the two results together.
          ///
          /// This technique is known as SWAR, which stands for "SIMD within a register". You can read more about it here:
          /// https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223
          ///
          /// @dev Notes:
          /// - The result is rounded toward zero.
          ///
          /// @param x The first operand as a UD60x18 number.
          /// @param y The second operand as a UD60x18 number.
          /// @return result The arithmetic average as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function avg(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              uint256 yUint = y.unwrap();
              unchecked {
                  result = wrap((xUint & yUint) + ((xUint ^ yUint) >> 1));
              }
          }
          /// @notice Yields the smallest whole number greater than or equal to x.
          ///
          /// @dev This is optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
          /// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
          ///
          /// Requirements:
          /// - x must be less than or equal to `MAX_WHOLE_UD60x18`.
          ///
          /// @param x The UD60x18 number to ceil.
          /// @param result The smallest whole number greater than or equal to x, as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function ceil(UD60x18 x) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              if (xUint > uMAX_WHOLE_UD60x18) {
                  revert Errors.PRBMath_UD60x18_Ceil_Overflow(x);
              }
              assembly ("memory-safe") {
                  // Equivalent to `x % UNIT`.
                  let remainder := mod(x, uUNIT)
                  // Equivalent to `UNIT - remainder`.
                  let delta := sub(uUNIT, remainder)
                  // Equivalent to `x + remainder > 0 ? delta : 0`.
                  result := add(x, mul(delta, gt(remainder, 0)))
              }
          }
          /// @notice Divides two UD60x18 numbers, returning a new UD60x18 number.
          ///
          /// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
          ///
          /// Notes:
          /// - Refer to the notes in {Common.mulDiv}.
          ///
          /// Requirements:
          /// - Refer to the requirements in {Common.mulDiv}.
          ///
          /// @param x The numerator as a UD60x18 number.
          /// @param y The denominator as a UD60x18 number.
          /// @param result The quotient as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function div(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(Common.mulDiv(x.unwrap(), uUNIT, y.unwrap()));
          }
          /// @notice Calculates the natural exponent of x using the following formula:
          ///
          /// $$
          /// e^x = 2^{x * log_2{e}}
          /// $$
          ///
          /// @dev Requirements:
          /// - x must be less than 133_084258667509499441.
          ///
          /// @param x The exponent as a UD60x18 number.
          /// @return result The result as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function exp(UD60x18 x) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              // This check prevents values greater than 192e18 from being passed to {exp2}.
              if (xUint > uEXP_MAX_INPUT) {
                  revert Errors.PRBMath_UD60x18_Exp_InputTooBig(x);
              }
              unchecked {
                  // Inline the fixed-point multiplication to save gas.
                  uint256 doubleUnitProduct = xUint * uLOG2_E;
                  result = exp2(wrap(doubleUnitProduct / uUNIT));
              }
          }
          /// @notice Calculates the binary exponent of x using the binary fraction method.
          ///
          /// @dev See https://ethereum.stackexchange.com/q/79903/24693
          ///
          /// Requirements:
          /// - x must be less than 192e18.
          /// - The result must fit in UD60x18.
          ///
          /// @param x The exponent as a UD60x18 number.
          /// @return result The result as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function exp2(UD60x18 x) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
              if (xUint > uEXP2_MAX_INPUT) {
                  revert Errors.PRBMath_UD60x18_Exp2_InputTooBig(x);
              }
              // Convert x to the 192.64-bit fixed-point format.
              uint256 x_192x64 = (xUint << 64) / uUNIT;
              // Pass x to the {Common.exp2} function, which uses the 192.64-bit fixed-point number representation.
              result = wrap(Common.exp2(x_192x64));
          }
          /// @notice Yields the greatest whole number less than or equal to x.
          /// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
          /// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
          /// @param x The UD60x18 number to floor.
          /// @param result The greatest whole number less than or equal to x, as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function floor(UD60x18 x) pure returns (UD60x18 result) {
              assembly ("memory-safe") {
                  // Equivalent to `x % UNIT`.
                  let remainder := mod(x, uUNIT)
                  // Equivalent to `x - remainder > 0 ? remainder : 0)`.
                  result := sub(x, mul(remainder, gt(remainder, 0)))
              }
          }
          /// @notice Yields the excess beyond the floor of x using the odd function definition.
          /// @dev See https://en.wikipedia.org/wiki/Fractional_part.
          /// @param x The UD60x18 number to get the fractional part of.
          /// @param result The fractional part of x as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function frac(UD60x18 x) pure returns (UD60x18 result) {
              assembly ("memory-safe") {
                  result := mod(x, uUNIT)
              }
          }
          /// @notice Calculates the geometric mean of x and y, i.e. $\\sqrt{x * y}$, rounding down.
          ///
          /// @dev Requirements:
          /// - x * y must fit in UD60x18.
          ///
          /// @param x The first operand as a UD60x18 number.
          /// @param y The second operand as a UD60x18 number.
          /// @return result The result as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function gm(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              uint256 yUint = y.unwrap();
              if (xUint == 0 || yUint == 0) {
                  return ZERO;
              }
              unchecked {
                  // Checking for overflow this way is faster than letting Solidity do it.
                  uint256 xyUint = xUint * yUint;
                  if (xyUint / xUint != yUint) {
                      revert Errors.PRBMath_UD60x18_Gm_Overflow(x, y);
                  }
                  // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
                  // during multiplication. See the comments in {Common.sqrt}.
                  result = wrap(Common.sqrt(xyUint));
              }
          }
          /// @notice Calculates the inverse of x.
          ///
          /// @dev Notes:
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - x must not be zero.
          ///
          /// @param x The UD60x18 number for which to calculate the inverse.
          /// @return result The inverse as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function inv(UD60x18 x) pure returns (UD60x18 result) {
              unchecked {
                  result = wrap(uUNIT_SQUARED / x.unwrap());
              }
          }
          /// @notice Calculates the natural logarithm of x using the following formula:
          ///
          /// $$
          /// ln{x} = log_2{x} / log_2{e}
          /// $$
          ///
          /// @dev Notes:
          /// - Refer to the notes in {log2}.
          /// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
          ///
          /// Requirements:
          /// - Refer to the requirements in {log2}.
          ///
          /// @param x The UD60x18 number for which to calculate the natural logarithm.
          /// @return result The natural logarithm as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function ln(UD60x18 x) pure returns (UD60x18 result) {
              unchecked {
                  // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
                  // {log2} can return is ~196_205294292027477728.
                  result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
              }
          }
          /// @notice Calculates the common logarithm of x using the following formula:
          ///
          /// $$
          /// log_{10}{x} = log_2{x} / log_2{10}
          /// $$
          ///
          /// However, if x is an exact power of ten, a hard coded value is returned.
          ///
          /// @dev Notes:
          /// - Refer to the notes in {log2}.
          ///
          /// Requirements:
          /// - Refer to the requirements in {log2}.
          ///
          /// @param x The UD60x18 number for which to calculate the common logarithm.
          /// @return result The common logarithm as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function log10(UD60x18 x) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              if (xUint < uUNIT) {
                  revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
              }
              // Note that the `mul` in this assembly block is the standard multiplication operation, not {UD60x18.mul}.
              // prettier-ignore
              assembly ("memory-safe") {
                  switch x
                  case 1 { result := mul(uUNIT, sub(0, 18)) }
                  case 10 { result := mul(uUNIT, sub(1, 18)) }
                  case 100 { result := mul(uUNIT, sub(2, 18)) }
                  case 1000 { result := mul(uUNIT, sub(3, 18)) }
                  case 10000 { result := mul(uUNIT, sub(4, 18)) }
                  case 100000 { result := mul(uUNIT, sub(5, 18)) }
                  case 1000000 { result := mul(uUNIT, sub(6, 18)) }
                  case 10000000 { result := mul(uUNIT, sub(7, 18)) }
                  case 100000000 { result := mul(uUNIT, sub(8, 18)) }
                  case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
                  case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
                  case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
                  case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
                  case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
                  case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
                  case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
                  case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
                  case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
                  case 1000000000000000000 { result := 0 }
                  case 10000000000000000000 { result := uUNIT }
                  case 100000000000000000000 { result := mul(uUNIT, 2) }
                  case 1000000000000000000000 { result := mul(uUNIT, 3) }
                  case 10000000000000000000000 { result := mul(uUNIT, 4) }
                  case 100000000000000000000000 { result := mul(uUNIT, 5) }
                  case 1000000000000000000000000 { result := mul(uUNIT, 6) }
                  case 10000000000000000000000000 { result := mul(uUNIT, 7) }
                  case 100000000000000000000000000 { result := mul(uUNIT, 8) }
                  case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
                  case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
                  case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
                  case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
                  case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
                  case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
                  case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
                  case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
                  case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
                  case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
                  case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
                  case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
                  case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
                  case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
                  case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
                  case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
                  case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
                  case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
                  case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
                  case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
                  case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
                  case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
                  case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
                  case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
                  case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
                  case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
                  case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
                  case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
                  case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
                  case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
                  case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
                  case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
                  case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
                  case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
                  case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
                  case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
                  case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
                  case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
                  case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
                  case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
                  case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
                  case 100000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 59) }
                  default { result := uMAX_UD60x18 }
              }
              if (result.unwrap() == uMAX_UD60x18) {
                  unchecked {
                      // Inline the fixed-point division to save gas.
                      result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
                  }
              }
          }
          /// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
          ///
          /// $$
          /// log_2{x} = n + log_2{y}, \\text{ where } y = x*2^{-n}, \\ y \\in [1, 2)
          /// $$
          ///
          /// For $0 \\leq x \\lt 1$, the input is inverted:
          ///
          /// $$
          /// log_2{x} = -log_2{\\frac{1}{x}}
          /// $$
          ///
          /// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation
          ///
          /// Notes:
          /// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
          ///
          /// Requirements:
          /// - x must be greater than zero.
          ///
          /// @param x The UD60x18 number for which to calculate the binary logarithm.
          /// @return result The binary logarithm as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function log2(UD60x18 x) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              if (xUint < uUNIT) {
                  revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
              }
              unchecked {
                  // Calculate the integer part of the logarithm.
                  uint256 n = Common.msb(xUint / uUNIT);
                  // This is the integer part of the logarithm as a UD60x18 number. The operation can't overflow because n
                  // n is at most 255 and UNIT is 1e18.
                  uint256 resultUint = n * uUNIT;
                  // Calculate $y = x * 2^{-n}$.
                  uint256 y = xUint >> n;
                  // If y is the unit number, the fractional part is zero.
                  if (y == uUNIT) {
                      return wrap(resultUint);
                  }
                  // Calculate the fractional part via the iterative approximation.
                  // The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
                  uint256 DOUBLE_UNIT = 2e18;
                  for (uint256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
                      y = (y * y) / uUNIT;
                      // Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
                      if (y >= DOUBLE_UNIT) {
                          // Add the 2^{-m} factor to the logarithm.
                          resultUint += delta;
                          // Halve y, which corresponds to z/2 in the Wikipedia article.
                          y >>= 1;
                      }
                  }
                  result = wrap(resultUint);
              }
          }
          /// @notice Multiplies two UD60x18 numbers together, returning a new UD60x18 number.
          ///
          /// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
          ///
          /// Notes:
          /// - Refer to the notes in {Common.mulDiv}.
          ///
          /// Requirements:
          /// - Refer to the requirements in {Common.mulDiv}.
          ///
          /// @dev See the documentation in {Common.mulDiv18}.
          /// @param x The multiplicand as a UD60x18 number.
          /// @param y The multiplier as a UD60x18 number.
          /// @return result The product as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function mul(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              result = wrap(Common.mulDiv18(x.unwrap(), y.unwrap()));
          }
          /// @notice Raises x to the power of y.
          ///
          /// For $1 \\leq x \\leq \\infty$, the following standard formula is used:
          ///
          /// $$
          /// x^y = 2^{log_2{x} * y}
          /// $$
          ///
          /// For $0 \\leq x \\lt 1$, since the unsigned {log2} is undefined, an equivalent formula is used:
          ///
          /// $$
          /// i = \\frac{1}{x}
          /// w = 2^{log_2{i} * y}
          /// x^y = \\frac{1}{w}
          /// $$
          ///
          /// @dev Notes:
          /// - Refer to the notes in {log2} and {mul}.
          /// - Returns `UNIT` for 0^0.
          /// - It may not perform well with very small values of x. Consider using SD59x18 as an alternative.
          ///
          /// Requirements:
          /// - Refer to the requirements in {exp2}, {log2}, and {mul}.
          ///
          /// @param x The base as a UD60x18 number.
          /// @param y The exponent as a UD60x18 number.
          /// @return result The result as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function pow(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              uint256 yUint = y.unwrap();
              // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
              if (xUint == 0) {
                  return yUint == 0 ? UNIT : ZERO;
              }
              // If x is `UNIT`, the result is always `UNIT`.
              else if (xUint == uUNIT) {
                  return UNIT;
              }
              // If y is zero, the result is always `UNIT`.
              if (yUint == 0) {
                  return UNIT;
              }
              // If y is `UNIT`, the result is always x.
              else if (yUint == uUNIT) {
                  return x;
              }
              // If x is greater than `UNIT`, use the standard formula.
              if (xUint > uUNIT) {
                  result = exp2(mul(log2(x), y));
              }
              // Conversely, if x is less than `UNIT`, use the equivalent formula.
              else {
                  UD60x18 i = wrap(uUNIT_SQUARED / xUint);
                  UD60x18 w = exp2(mul(log2(i), y));
                  result = wrap(uUNIT_SQUARED / w.unwrap());
              }
          }
          /// @notice Raises x (a UD60x18 number) to the power y (an unsigned basic integer) using the well-known
          /// algorithm "exponentiation by squaring".
          ///
          /// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
          ///
          /// Notes:
          /// - Refer to the notes in {Common.mulDiv18}.
          /// - Returns `UNIT` for 0^0.
          ///
          /// Requirements:
          /// - The result must fit in UD60x18.
          ///
          /// @param x The base as a UD60x18 number.
          /// @param y The exponent as a uint256.
          /// @return result The result as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function powu(UD60x18 x, uint256 y) pure returns (UD60x18 result) {
              // Calculate the first iteration of the loop in advance.
              uint256 xUint = x.unwrap();
              uint256 resultUint = y & 1 > 0 ? xUint : uUNIT;
              // Equivalent to `for(y /= 2; y > 0; y /= 2)`.
              for (y >>= 1; y > 0; y >>= 1) {
                  xUint = Common.mulDiv18(xUint, xUint);
                  // Equivalent to `y % 2 == 1`.
                  if (y & 1 > 0) {
                      resultUint = Common.mulDiv18(resultUint, xUint);
                  }
              }
              result = wrap(resultUint);
          }
          /// @notice Calculates the square root of x using the Babylonian method.
          ///
          /// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
          ///
          /// Notes:
          /// - The result is rounded toward zero.
          ///
          /// Requirements:
          /// - x must be less than `MAX_UD60x18 / UNIT`.
          ///
          /// @param x The UD60x18 number for which to calculate the square root.
          /// @return result The result as a UD60x18 number.
          /// @custom:smtchecker abstract-function-nondet
          function sqrt(UD60x18 x) pure returns (UD60x18 result) {
              uint256 xUint = x.unwrap();
              unchecked {
                  if (xUint > uMAX_UD60x18 / uUNIT) {
                      revert Errors.PRBMath_UD60x18_Sqrt_Overflow(x);
                  }
                  // Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two UD60x18 numbers.
                  // In this case, the two numbers are both the square root.
                  result = wrap(Common.sqrt(xUint * uUNIT));
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.19;
          import "./Casting.sol" as Casting;
          import "./Helpers.sol" as Helpers;
          import "./Math.sol" as Math;
          /// @notice The unsigned 60.18-decimal fixed-point number representation, which can have up to 60 digits and up to 18
          /// decimals. The values of this are bound by the minimum and the maximum values permitted by the Solidity type uint256.
          /// @dev The value type is defined here so it can be imported in all other files.
          type UD60x18 is uint256;
          /*//////////////////////////////////////////////////////////////////////////
                                              CASTING
          //////////////////////////////////////////////////////////////////////////*/
          using {
              Casting.intoSD1x18,
              Casting.intoUD2x18,
              Casting.intoSD59x18,
              Casting.intoUint128,
              Casting.intoUint256,
              Casting.intoUint40,
              Casting.unwrap
          } for UD60x18 global;
          /*//////////////////////////////////////////////////////////////////////////
                                      MATHEMATICAL FUNCTIONS
          //////////////////////////////////////////////////////////////////////////*/
          // The global "using for" directive makes the functions in this library callable on the UD60x18 type.
          using {
              Math.avg,
              Math.ceil,
              Math.div,
              Math.exp,
              Math.exp2,
              Math.floor,
              Math.frac,
              Math.gm,
              Math.inv,
              Math.ln,
              Math.log10,
              Math.log2,
              Math.mul,
              Math.pow,
              Math.powu,
              Math.sqrt
          } for UD60x18 global;
          /*//////////////////////////////////////////////////////////////////////////
                                          HELPER FUNCTIONS
          //////////////////////////////////////////////////////////////////////////*/
          // The global "using for" directive makes the functions in this library callable on the UD60x18 type.
          using {
              Helpers.add,
              Helpers.and,
              Helpers.eq,
              Helpers.gt,
              Helpers.gte,
              Helpers.isZero,
              Helpers.lshift,
              Helpers.lt,
              Helpers.lte,
              Helpers.mod,
              Helpers.neq,
              Helpers.not,
              Helpers.or,
              Helpers.rshift,
              Helpers.sub,
              Helpers.uncheckedAdd,
              Helpers.uncheckedSub,
              Helpers.xor
          } for UD60x18 global;
          /*//////////////////////////////////////////////////////////////////////////
                                              OPERATORS
          //////////////////////////////////////////////////////////////////////////*/
          // The global "using for" directive makes it possible to use these operators on the UD60x18 type.
          using {
              Helpers.add as +,
              Helpers.and2 as &,
              Math.div as /,
              Helpers.eq as ==,
              Helpers.gt as >,
              Helpers.gte as >=,
              Helpers.lt as <,
              Helpers.lte as <=,
              Helpers.or as |,
              Helpers.mod as %,
              Math.mul as *,
              Helpers.neq as !=,
              Helpers.not as ~,
              Helpers.sub as -,
              Helpers.xor as ^
          } for UD60x18 global;
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev DonatorBlacklist interface.
          interface IDonatorBlacklist {
              /// @dev Gets account blacklisting status.
              /// @param account Account address.
              /// @return status Blacklisting status.
              function isDonatorBlacklisted(address account) external view returns (bool status);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @dev Errors.
          interface IErrorsTokenomics {
              /// @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 Non-zero value when it has to be zero.
              error NonZeroValue();
              /// @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 Token is disabled or not whitelisted.
              /// @param tokenAddress Address of a token.
              error UnauthorizedToken(address tokenAddress);
              /// @dev Provided token address is incorrect.
              /// @param provided Provided token address.
              /// @param expected Expected token address.
              error WrongTokenAddress(address provided, address expected);
              /// @dev Bond is not redeemable (does not exist or not matured).
              /// @param bondId Bond Id.
              error BondNotRedeemable(uint256 bondId);
              /// @dev The product is expired.
              /// @param tokenAddress Address of a token.
              /// @param productId Product Id.
              /// @param deadline The program expiry time.
              /// @param curTime Current timestamp.
              error ProductExpired(address tokenAddress, uint256 productId, uint256 deadline, uint256 curTime);
              /// @dev The product is already closed.
              /// @param productId Product Id.
              error ProductClosed(uint256 productId);
              /// @dev The product supply is low for the requested payout.
              /// @param tokenAddress Address of a token.
              /// @param productId Product Id.
              /// @param requested Requested payout.
              /// @param actual Actual supply left.
              error ProductSupplyLow(address tokenAddress, uint256 productId, uint256 requested, uint256 actual);
              /// @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 Insufficient token allowance.
              /// @param provided Provided amount.
              /// @param expected Minimum expected amount.
              error InsufficientAllowance(uint256 provided, uint256 expected);
              /// @dev Failure of a transfer.
              /// @param token Address of a token.
              /// @param from Address `from`.
              /// @param to Address `to`.
              /// @param amount Token amount.
              error TransferFailed(address token, address from, address to, uint256 amount);
              /// @dev Incentives claim has failed.
              /// @param account Account address.
              /// @param reward Reward amount.
              /// @param topUp Top-up amount.
              error ClaimIncentivesFailed(address account, uint256 reward, uint256 topUp);
              /// @dev Caught reentrancy violation.
              error ReentrancyGuard();
              /// @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);
              /// @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 The contract is paused.
              error Paused();
              /// @dev Caught an operation that is not supposed to happen in the same block.
              error SameBlockNumberViolation();
          }
          // 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);
                  }
                  // Check if the donator blacklist is enabled, and the status of the donator address
                  address bList = donatorBlacklist;
                  if (bList != address(0) && IDonatorBlacklist(bList).isDonatorBlacklisted(donator)) {
                      revert DonatorBlacklisted(donator);
                  }
                  // Get the number of services
                  uint256 numServices = serviceIds.length;
                  // Loop over service Ids, accumulate donation value and check for the service existence
                  for (uint256 i = 0; i < numServices; ++i) {
                      // Check for the service Id existence
                      if (!IServiceRegistry(serviceRegistry).exists(serviceIds[i])) {
                          revert ServiceDoesNotExist(serviceIds[i]);
                      }
                  }
                  // Get the current epoch
                  uint256 curEpoch = epochCounter;
                  // Increase the total service donation balance per epoch
                  donationETH += mapEpochTokenomics[curEpoch].epochPoint.totalDonationsETH;
                  mapEpochTokenomics[curEpoch].epochPoint.totalDonationsETH = uint96(donationETH);
                  // Track service donations
                  _trackServiceDonations(donator, serviceIds, amounts, curEpoch);
                  // Set the current block number
                  lastDonationBlockNumber = uint32(block.number);
              }
              /// @dev Gets the inverse discount factor value.
              /// @param treasuryRewards Treasury rewards.
              /// @param numNewOwners Number of new owners of components / agents registered during the epoch.
              /// @return idf IDF value.
              function _calculateIDF(uint256 treasuryRewards, uint256 numNewOwners) internal view returns (uint256 idf) {
                  idf = 0;
                  // Calculate the inverse discount factor based on the tokenomics parameters and values of units per epoch
                  // df = 1 / (1 + iterest_rate), idf = (1 + iterest_rate) >= 1.0
                  // Calculate IDF from epsilon rate and f(K,D)
                  // f(K(e), D(e)) = d * k * K(e) + d * D(e),
                  // where d corresponds to codePerDev and k corresponds to devPerCapital
                  // codeUnits (codePerDev) is the estimated value of the code produced by a single developer for epoch
                  UD60x18 codeUnits = UD60x18.wrap(codePerDev);
                  // fKD = codeUnits * devsPerCapital * treasuryRewards + codeUnits * newOwners;
                  // Convert all the necessary values to fixed-point numbers considering OLAS decimals (18 by default)
                  UD60x18 fp = UD60x18.wrap(treasuryRewards);
                  // Convert devsPerCapital
                  UD60x18 fpDevsPerCapital = UD60x18.wrap(devsPerCapital);
                  fp = fp.mul(fpDevsPerCapital);
                  UD60x18 fpNumNewOwners = convert(numNewOwners);
                  fp = fp.add(fpNumNewOwners);
                  fp = fp.mul(codeUnits);
                  // fp = fp / 100 - calculate the final value in fixed point
                  fp = fp.div(UD60x18.wrap(100e18));
                  // fKD in the state that is comparable with epsilon rate
                  uint256 fKD = UD60x18.unwrap(fp);
                  // Compare with epsilon rate and choose the smallest one
                  if (fKD > epsilonRate) {
                      fKD = epsilonRate;
                  }
                  // 1 + fKD in the system where 1e18 is equal to a whole unit (18 decimals)
                  idf = 1e18 + fKD;
              }
              /// @dev Record global data with a new checkpoint.
              /// @notice Note that even though a specific epoch can last longer than the epochLen, it is practically
              ///         not valid not to call a checkpoint for longer than a year. Thus, the function will return false otherwise.
              /// @notice Checkpoint must not be called in the same block with the service donation.
              /// @return True if the function execution is successful.
              /// #if_succeeds {:msg "epochCounter can only increase"} $result == true ==> epochCounter == old(epochCounter) + 1;
              /// #if_succeeds {:msg "two events will never happen at the same time"} $result == true && (block.timestamp - timeLaunch) / ONE_YEAR > old(currentYear) ==> currentYear == old(currentYear) + 1;
              /// #if_succeeds {:msg "previous epoch endTime must never be zero"} mapEpochTokenomics[epochCounter - 1].epochPoint.endTime > 0;
              /// #if_succeeds {:msg "when the year is the same, the adjusted maxBond (incentives[4]) will never be lower than the epoch maxBond"}
              ///$result == true && (block.timestamp - timeLaunch) / ONE_YEAR == old(currentYear)
              /// ==> old((inflationPerSecond * (block.timestamp - mapEpochTokenomics[epochCounter - 1].epochPoint.endTime) * mapEpochTokenomics[epochCounter].epochPoint.maxBondFraction) / 100) >= old(maxBond);
              /// #if_succeeds {:msg "idf check"} $result == true ==> mapEpochTokenomics[epochCounter].epochPoint.idf >= 1e18 && mapEpochTokenomics[epochCounter].epochPoint.idf <= 18e18;
              /// #if_succeeds {:msg "devsPerCapital check"} $result == true ==> devsPerCapital > MIN_PARAM_VALUE;
              /// #if_succeeds {:msg "codePerDev check"} $result == true ==> codePerDev > MIN_PARAM_VALUE;
              /// #if_succeeds {:msg "sum of reward fractions must result in 100"} $result == true
              /// ==> mapEpochTokenomics[epochCounter].unitPoints[0].rewardUnitFraction + mapEpochTokenomics[epochCounter].unitPoints[1].rewardUnitFraction + mapEpochTokenomics[epochCounter].epochPoint.rewardTreasuryFraction == 100;
              function checkpoint() external returns (bool) {
                  // Get the implementation address that was written to the proxy contract
                  address implementation;
                  assembly {
                      implementation := sload(PROXY_TOKENOMICS)
                  }
                  // Check if there is any address in the PROXY_TOKENOMICS address slot
                  if (implementation == address(0)) {
                      revert DelegatecallOnly();
                  }
                  // Check the last donation block number to avoid the possibility of a flash loan attack
                  if (lastDonationBlockNumber == block.number) {
                      revert SameBlockNumberViolation();
                  }
                  // New point can be calculated only if we passed the number of blocks equal to the epoch length
                  uint256 prevEpochTime = mapEpochTokenomics[epochCounter - 1].epochPoint.endTime;
                  uint256 diffNumSeconds = block.timestamp - prevEpochTime;
                  uint256 curEpochLen = epochLen;
                  // Check if the time passed since the last epoch end time is bigger than the specified epoch length,
                  // but not bigger than almost a year in seconds
                  if (diffNumSeconds < curEpochLen || diffNumSeconds > MAX_EPOCH_LENGTH) {
                      return false;
                  }
                  uint256 eCounter = epochCounter;
                  TokenomicsPoint storage tp = mapEpochTokenomics[eCounter];
                  // 0: total incentives funded with donations in ETH, that are split between:
                  // 1: treasuryRewards, 2: componentRewards, 3: agentRewards
                  // OLAS inflation is split between:
                  // 4: maxBond, 5: component ownerTopUps, 6: agent ownerTopUps
                  // 7: returned staking incentive from previous epochs, 8: staking incentive
                  uint256[] memory incentives = new uint256[](9);
                  incentives[0] = tp.epochPoint.totalDonationsETH;
                  incentives[1] = (incentives[0] * tp.epochPoint.rewardTreasuryFraction) / 100;
                  // 0 stands for components and 1 for agents
                  incentives[2] = (incentives[0] * tp.unitPoints[0].rewardUnitFraction) / 100;
                  incentives[3] = (incentives[0] * tp.unitPoints[1].rewardUnitFraction) / 100;
                  // The actual inflation per epoch considering that it is settled not in the exact epochLen time, but a bit later
                  uint256 inflationPerEpoch;
                  // Record the current inflation per second
                  uint256 curInflationPerSecond = inflationPerSecond;
                  // Current year
                  uint256 numYears = (block.timestamp - timeLaunch) / ONE_YEAR;
                  // Amounts for the yearly inflation change from year to year, so if the year changes in the middle
                  // of the epoch, it is necessary to adjust epoch inflation numbers to account for the year change
                  if (numYears > currentYear) {
                      // Calculate remainder of inflation for the passing year
                      // End of the year timestamp
                      uint256 yearEndTime = timeLaunch + numYears * ONE_YEAR;
                      // Initial inflation per epoch during the end of the year minus previous epoch timestamp
                      inflationPerEpoch = (yearEndTime - prevEpochTime) * curInflationPerSecond;
                      // Recalculate the inflation per second based on the new inflation for the current year
                      curInflationPerSecond = getInflationForYear(numYears) / ONE_YEAR;
                      // Add the remainder of inflation amount for this epoch based on a new inflation per second ratio
                      inflationPerEpoch += (block.timestamp - yearEndTime) * curInflationPerSecond;
                      // Updating state variables
                      inflationPerSecond = uint96(curInflationPerSecond);
                      currentYear = uint8(numYears);
                      // Set the tokenomics parameters flag such that the maxBond is correctly updated below (3rd bit is set to one)
                      tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x04;
                  } else {
                      // Inflation per epoch is equal to the inflation per second multiplied by the actual time of the epoch
                      inflationPerEpoch = curInflationPerSecond * diffNumSeconds;
                  }
                  // Bonding and top-ups in OLAS are recalculated based on the inflation schedule per epoch
                  // Actual maxBond of the epoch
                  tp.epochPoint.totalTopUpsOLAS = uint96(inflationPerEpoch);
                  incentives[4] = (inflationPerEpoch * tp.epochPoint.maxBondFraction) / 100;
                  // Get the maxBond that was credited to effectiveBond during this settled epoch
                  // If the year changes, the maxBond for the next epoch is updated in the condition below and will be used
                  // later when the effectiveBond is updated for the next epoch
                  uint256 curMaxBond = maxBond;
                  // Effective bond accumulates bonding leftovers from previous epochs (with the last max bond value set)
                  // It is given the value of the maxBond for the next epoch as a credit
                  // The difference between recalculated max bond per epoch and maxBond value must be reflected in effectiveBond,
                  // since the epoch checkpoint delay was not accounted for initially
                  // This has to be always true, or incentives[4] == curMaxBond if the epoch is settled exactly at the epochLen time
                  if (incentives[4] > curMaxBond) {
                      // Adjust the effectiveBond
                      incentives[4] = effectiveBond + incentives[4] - curMaxBond;
                      effectiveBond = uint96(incentives[4]);
                  }
                  // Get the tokenomics point of the next epoch
                  TokenomicsPoint storage nextEpochPoint = mapEpochTokenomics[eCounter + 1];
                  // Update parameters for the next epoch, if changes were requested by the changeTokenomicsParameters() function
                  // Check if the second bit is set to one
                  if (tokenomicsParametersUpdated & 0x01 == 0x01) {
                      // Update epoch length and set the next value back to zero
                      if (nextEpochLen > 0) {
                          curEpochLen = nextEpochLen;
                          epochLen = uint32(curEpochLen);
                          nextEpochLen = 0;
                      }
                      // Update veOLAS threshold and set the next value back to zero
                      if (nextVeOLASThreshold > 0) {
                          veOLASThreshold = nextVeOLASThreshold;
                          nextVeOLASThreshold = 0;
                      }
                      // Confirm the change of tokenomics parameters
                      emit TokenomicsParametersUpdated(eCounter + 1);
                  }
                  // Update incentive fractions for the next epoch if they were requested by the changeIncentiveFractions() function
                  // Check if the second bit is set to one
                  if (tokenomicsParametersUpdated & 0x02 == 0x02) {
                      // Confirm the change of incentive fractions
                      emit IncentiveFractionsUpdated(eCounter + 1);
                  } else {
                      // Copy current tokenomics point into the next one such that it has necessary tokenomics parameters
                      for (uint256 i = 0; i < 2; ++i) {
                          nextEpochPoint.unitPoints[i].topUpUnitFraction = tp.unitPoints[i].topUpUnitFraction;
                          nextEpochPoint.unitPoints[i].rewardUnitFraction = tp.unitPoints[i].rewardUnitFraction;
                      }
                      nextEpochPoint.epochPoint.rewardTreasuryFraction = tp.epochPoint.rewardTreasuryFraction;
                      nextEpochPoint.epochPoint.maxBondFraction = tp.epochPoint.maxBondFraction;
                      // Copy service staking fraction
                      mapEpochStakingPoints[eCounter + 1].stakingFraction = mapEpochStakingPoints[eCounter].stakingFraction;
                  }
                  // Update service staking parameters if they were requested by the changeStakingParams() function
                  // Check if the forth bit is set to one
                  if (tokenomicsParametersUpdated & 0x08 == 0x08) {
                      // Confirm the change of service staking parameters
                      emit StakingParamsUpdated(eCounter + 1);
                  } else {
                      // Copy current service staking parameters into the next epoch
                      mapEpochStakingPoints[eCounter + 1].maxStakingIncentive = mapEpochStakingPoints[eCounter].maxStakingIncentive;
                      mapEpochStakingPoints[eCounter + 1].minStakingWeight = mapEpochStakingPoints[eCounter].minStakingWeight;
                  }
                  // Record settled epoch timestamp
                  tp.epochPoint.endTime = uint32(block.timestamp);
                  // Cumulative incentives
                  uint256 accountRewards = incentives[2] + incentives[3];
                  // Owner top-ups: epoch incentives for component owners funded with the inflation
                  incentives[5] = (inflationPerEpoch * tp.unitPoints[0].topUpUnitFraction) / 100;
                  // Owner top-ups: epoch incentives for agent owners funded with the inflation
                  incentives[6] = (inflationPerEpoch * tp.unitPoints[1].topUpUnitFraction) / 100;
                  // Even if there was no single donating service owner that had a sufficient veOLAS balance,
                  // we still record the amount of OLAS allocated for component / agent owner top-ups from the inflation schedule.
                  // This amount will appear in the EpochSettled event, and thus can be tracked historically
                  uint256 accountTopUps = incentives[5] + incentives[6];
                  // Service staking funding
                  // Refunded amount during the epoch
                  incentives[7] = mapEpochStakingPoints[eCounter].stakingIncentive;
                  // Adding service staking top-ups amount based on a current epoch inflation
                  incentives[8] = incentives[7] + (inflationPerEpoch * mapEpochStakingPoints[eCounter].stakingFraction) / 100;
                  mapEpochStakingPoints[eCounter].stakingIncentive = uint96(incentives[8]);
                  // Adjust max bond value if the next epoch is going to be the year change epoch
                  // Note that this computation happens before the epoch that is triggered in the next epoch (the code above) when
                  // the actual year changes
                  numYears = (block.timestamp + curEpochLen - timeLaunch) / ONE_YEAR;
                  // Account for the year change to adjust the max bond
                  if (numYears > currentYear) {
                      // Calculate the inflation remainder for the passing year
                      // End of the year timestamp
                      uint256 yearEndTime = timeLaunch + numYears * ONE_YEAR;
                      // Calculate the inflation per epoch value until the end of the year
                      inflationPerEpoch = (yearEndTime - block.timestamp) * curInflationPerSecond;
                      // Recalculate the inflation per second based on the new inflation for the current year
                      curInflationPerSecond = getInflationForYear(numYears) / ONE_YEAR;
                      // Add the remainder of the inflation for the next epoch based on a new inflation per second ratio
                      inflationPerEpoch += (block.timestamp + curEpochLen - yearEndTime) * curInflationPerSecond;
                      // Calculate the max bond value
                      curMaxBond = (inflationPerEpoch * nextEpochPoint.epochPoint.maxBondFraction) / 100;
                      // Update state maxBond value
                      maxBond = uint96(curMaxBond);
                      // Reset the tokenomics parameters update flag
                      tokenomicsParametersUpdated = 0;
                  } else if (tokenomicsParametersUpdated > 0) {
                      // Since tokenomics parameters have been updated, maxBond has to be recalculated
                      curMaxBond = (curEpochLen * curInflationPerSecond * nextEpochPoint.epochPoint.maxBondFraction) / 100;
                      // Update state maxBond value
                      maxBond = uint96(curMaxBond);
                      // Reset the tokenomics parameters update flag
                      tokenomicsParametersUpdated = 0;
                  }
                  // Update effectiveBond with the current or updated maxBond value
                  curMaxBond += effectiveBond;
                  effectiveBond = uint96(curMaxBond);
                  // Update the IDF value for the next epoch or assign a default one if there are no ETH donations
                  if (incentives[0] > 0) {
                      // Calculate IDF based on the incoming donations
                      uint256 idf = _calculateIDF(incentives[1], tp.epochPoint.numNewOwners);
                      nextEpochPoint.epochPoint.idf = uint64(idf);
                      emit IDFUpdated(idf);
                  } else {
                      // Assign a default IDF value
                      nextEpochPoint.epochPoint.idf = 1e18;
                  }
                  // Treasury contract rebalances ETH funds depending on the treasury rewards
                  if (incentives[1] == 0 || ITreasury(treasury).rebalanceTreasury(incentives[1])) {
                      // Emit settled epoch written to the last economics point
                      emit EpochSettled(eCounter, incentives[1], accountRewards, accountTopUps, curMaxBond, incentives[7],
                          incentives[8]);
                      // Start new epoch
                      epochCounter = uint32(eCounter + 1);
                  } else {
                      // If the treasury rebalance was not executed correctly, the new epoch does not start
                      revert TreasuryRebalanceFailed(eCounter);
                  }
                  return true;
              }
              /// @dev Gets component / agent owner incentives and clears the balances.
              /// @notice `account` must be the owner of components / agents Ids, otherwise the function will revert.
              /// @notice If not all `unitIds` belonging to `account` were provided, they will be untouched and keep accumulating.
              /// @notice Component and agent Ids must be provided in the ascending order and must not repeat.
              /// @param account Account address.
              /// @param unitTypes Set of unit types (component / agent).
              /// @param unitIds Set of corresponding unit Ids where account is the owner.
              /// @return reward Reward amount.
              /// @return topUp Top-up amount.
              function accountOwnerIncentives(
                  address account,
                  uint256[] memory unitTypes,
                  uint256[] memory unitIds
              ) external returns (uint256 reward, uint256 topUp) {
                  // Check for the dispenser access
                  if (dispenser != msg.sender) {
                      revert ManagerOnly(msg.sender, dispenser);
                  }
                  // Check array lengths
                  if (unitTypes.length != unitIds.length) {
                      revert WrongArrayLength(unitTypes.length, unitIds.length);
                  }
                  // Component / agent registry addresses
                  address[] memory registries = new address[](2);
                  (registries[0], registries[1]) = (componentRegistry, agentRegistry);
                  // Component / agent total supply
                  uint256[] memory registriesSupply = new uint256[](2);
                  for (uint256 i = 0; i < 2; ++i) {
                      registriesSupply[i] = IToken(registries[i]).totalSupply();
                  }
                  // Check the input data
                  uint256[] memory lastIds = new uint256[](2);
                  for (uint256 i = 0; i < unitIds.length; ++i) {
                      // Check for the unit type to be component / agent only
                      if (unitTypes[i] > 1) {
                          revert Overflow(unitTypes[i], 1);
                      }
                      // Check that the unit Ids are in ascending order, not repeating, and no bigger than registries total supply
                      if (unitIds[i] <= lastIds[unitTypes[i]] || unitIds[i] > registriesSupply[unitTypes[i]]) {
                          revert WrongUnitId(unitIds[i], unitTypes[i]);
                      }
                      lastIds[unitTypes[i]] = unitIds[i];
                      // Check the component / agent Id ownership
                      address unitOwner = IToken(registries[unitTypes[i]]).ownerOf(unitIds[i]);
                      if (unitOwner != account) {
                          revert OwnerOnly(unitOwner, account);
                      }
                  }
                  // Get the current epoch counter
                  uint256 curEpoch = epochCounter;
                  for (uint256 i = 0; i < unitIds.length; ++i) {
                      // Get the last epoch number the incentives were accumulated for
                      uint256 lastEpoch = mapUnitIncentives[unitTypes[i]][unitIds[i]].lastEpoch;
                      // Finalize unit rewards and top-ups if there were pending ones from the previous epoch
                      // The finalization is needed when the trackServiceDonations() function did not take care of it
                      // since between last epoch the donations were received and this current epoch there were no more donations
                      if (lastEpoch > 0 && lastEpoch < curEpoch) {
                          _finalizeIncentivesForUnitId(lastEpoch, unitTypes[i], unitIds[i]);
                          // Change the last epoch number
                          mapUnitIncentives[unitTypes[i]][unitIds[i]].lastEpoch = 0;
                      }
                      // Accumulate total rewards and clear their balances
                      reward += mapUnitIncentives[unitTypes[i]][unitIds[i]].reward;
                      mapUnitIncentives[unitTypes[i]][unitIds[i]].reward = 0;
                      // Accumulate total top-ups and clear their balances
                      topUp += mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp;
                      mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp = 0;
                  }
              }
              /// @dev Gets the component / agent owner incentives.
              /// @notice `account` must be the owner of components / agents they are passing, otherwise the function will revert.
              /// @param account Account address.
              /// @param unitTypes Set of unit types (component / agent).
              /// @param unitIds Set of corresponding unit Ids where account is the owner.
              /// @return reward Reward amount.
              /// @return topUp Top-up amount.
              function getOwnerIncentives(
                  address account,
                  uint256[] memory unitTypes,
                  uint256[] memory unitIds
              ) external view returns (uint256 reward, uint256 topUp) {
                  // Check array lengths
                  if (unitTypes.length != unitIds.length) {
                      revert WrongArrayLength(unitTypes.length, unitIds.length);
                  }
                  // Component / agent registry addresses
                  address[] memory registries = new address[](2);
                  (registries[0], registries[1]) = (componentRegistry, agentRegistry);
                  // Component / agent total supply
                  uint256[] memory registriesSupply = new uint256[](2);
                  for (uint256 i = 0; i < 2; ++i) {
                      registriesSupply[i] = IToken(registries[i]).totalSupply();
                  }
                  // Check the input data
                  uint256[] memory lastIds = new uint256[](2);
                  for (uint256 i = 0; i < unitIds.length; ++i) {
                      // Check for the unit type to be component / agent only
                      if (unitTypes[i] > 1) {
                          revert Overflow(unitTypes[i], 1);
                      }
                      // Check that the unit Ids are in ascending order, not repeating, and no bigger than registries total supply
                      if (unitIds[i] <= lastIds[unitTypes[i]] || unitIds[i] > registriesSupply[unitTypes[i]]) {
                          revert WrongUnitId(unitIds[i], unitTypes[i]);
                      }
                      lastIds[unitTypes[i]] = unitIds[i];
                      // Check the component / agent Id ownership
                      address unitOwner = IToken(registries[unitTypes[i]]).ownerOf(unitIds[i]);
                      if (unitOwner != account) {
                          revert OwnerOnly(unitOwner, account);
                      }
                  }
                  // Get the current epoch counter
                  uint256 curEpoch = epochCounter;
                  for (uint256 i = 0; i < unitIds.length; ++i) {
                      // Get the last epoch number the incentives were accumulated for
                      uint256 lastEpoch = mapUnitIncentives[unitTypes[i]][unitIds[i]].lastEpoch;
                      // Calculate rewards and top-ups if there were pending ones from the previous epoch
                      if (lastEpoch > 0 && lastEpoch < curEpoch) {
                          // Get the overall amount of unit rewards for the component's last epoch
                          // reward = (pendingRelativeReward * rewardUnitFraction) / 100
                          uint256 totalIncentives = mapUnitIncentives[unitTypes[i]][unitIds[i]].pendingRelativeReward;
                          if (totalIncentives > 0) {
                              totalIncentives *= mapEpochTokenomics[lastEpoch].unitPoints[unitTypes[i]].rewardUnitFraction;
                              // Accumulate to the final reward for the last epoch
                              reward += totalIncentives / 100;
                          }
                          // Add the final top-up for the last epoch
                          totalIncentives = mapUnitIncentives[unitTypes[i]][unitIds[i]].pendingRelativeTopUp;
                          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[lastEpoch].epochPoint.totalTopUpsOLAS;
                              totalIncentives *= mapEpochTokenomics[lastEpoch].unitPoints[unitTypes[i]].topUpUnitFraction;
                              uint256 sumUnitIncentives = uint256(mapEpochTokenomics[lastEpoch].unitPoints[unitTypes[i]].sumUnitTopUpsOLAS) * 100;
                              // Accumulate to the final top-up for the last epoch
                              topUp += totalIncentives / sumUnitIncentives;
                          }
                      }
                      // Accumulate total rewards to finalized ones
                      reward += mapUnitIncentives[unitTypes[i]][unitIds[i]].reward;
                      // Accumulate total top-ups to finalized ones
                      topUp += mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp;
                  }
              }
              /// @dev Gets component / agent point of a specified epoch number and a unit type.
              /// @param epoch Epoch number.
              /// @param unitType Component (0) or agent (1).
              /// @return Unit point.
              function getUnitPoint(uint256 epoch, uint256 unitType) external view returns (UnitPoint memory) {
                  return mapEpochTokenomics[epoch].unitPoints[unitType];
              }
              /// @dev Gets inverse discount factor with the multiple of 1e18 of the last epoch.
              /// @return Discount factor with the multiple of 1e18.
              function getLastIDF() external view returns (uint256) {
                  return mapEpochTokenomics[epochCounter].epochPoint.idf;
              }
              /// @dev Gets epoch end time.
              /// @param epoch Epoch number.
              /// @return Epoch end time.
              function getEpochEndTime(uint256 epoch) external view returns (uint256) {
                  return mapEpochTokenomics[epoch].epochPoint.endTime;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.25;
          /// @title TokenomicsConstants - Smart contract with tokenomics constants
          /// @author Aleksandr Kuperman - <[email protected]>
          /// @author Andrey Lebedev - <[email protected]>
          /// @author Mariapia Moscatiello - <[email protected]>
          abstract contract TokenomicsConstants {
              // Tokenomics version number
              string public constant VERSION = "1.2.0";
              // Tokenomics proxy address slot
              // keccak256("PROXY_TOKENOMICS") = "0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f"
              bytes32 public constant PROXY_TOKENOMICS = 0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f;
              // One year in seconds
              uint256 public constant ONE_YEAR = 1 days * 365;
              // Minimum epoch length
              uint256 public constant MIN_EPOCH_LENGTH = 10 days;
              // Max epoch length
              uint256 public constant MAX_EPOCH_LENGTH = ONE_YEAR - 1 days;
              // Minimum fixed point tokenomics parameters
              uint256 public constant MIN_PARAM_VALUE = 1e14;
              // Max staking weight amount
              uint256 public constant MAX_STAKING_WEIGHT = 10_000;
              /// @dev Gets an inflation cap for a specific year.
              /// @param numYears Number of years passed from the launch date.
              /// @return supplyCap Supply cap.
              /// supplyCap = 1e27 * (1.02)^(x-9) for x >= 10
              /// if_succeeds {:msg "correct supplyCap"} (numYears >= 10) ==> (supplyCap > 1e27);  
              /// There is a bug in scribble tools, a broken instrumented version is as follows:
              /// function getSupplyCapForYear(uint256 numYears) public returns (uint256 supplyCap)
              /// And the test is waiting for a view / pure function, which would be correct
              function getSupplyCapForYear(uint256 numYears) public pure returns (uint256 supplyCap) {
                  // For the first 10 years the supply caps are pre-defined
                  if (numYears < 10) {
                      uint96[10] memory supplyCaps = [
                          529_659_000e18,
                          569_913_084e18,
                          610_313_084e18,
                          666_313_084e18,
                          746_313_084e18,
                          818_313_084e18,
                          882_313_084e18,
                          930_313_084e18,
                          970_313_084e18,
                          1_000_000_000e18
                      ];
                      supplyCap = supplyCaps[numYears];
                  } else {
                      // Number of years after ten years have passed (including ongoing ones)
                      numYears -= 9;
                      // Max cap for the first 10 years
                      supplyCap = 1_000_000_000e18;
                      // After that the inflation is 2% per year as defined by the OLAS contract
                      uint256 maxMintCapFraction = 2;
                      // Get the supply cap until the current year
                      for (uint256 i = 0; i < numYears; ++i) {
                          supplyCap += (supplyCap * maxMintCapFraction) / 100;
                      }
                      // Return the difference between last two caps (inflation for the current year)
                      return supplyCap;
                  }
              }
              /// @dev Gets an inflation amount for a specific year.
              /// @param numYears Number of years passed from the launch date.
              /// @return inflationAmount Inflation limit amount.
              function getInflationForYear(uint256 numYears) public pure returns (uint256 inflationAmount) {
                  // For the first 10 years the inflation caps are pre-defined as differences between next year cap and current year one
                  if (numYears < 10) {
                      // Initial OLAS allocation is 526_500_000_0e17
                      uint88[10] memory inflationAmounts = [
                          3_159_000e18,
                          40_254_084e18,
                          40_400_000e18,
                          56_000_000e18,
                          80_000_000e18,
                          72_000_000e18,
                          64_000_000e18,
                          48_000_000e18,
                          40_000_000e18,
                          29_686_916e18
                      ];
                      inflationAmount = inflationAmounts[numYears];
                  } else {
                      // Number of years after ten years have passed (including ongoing ones)
                      numYears -= 9;
                      // Max cap for the first 10 years
                      uint256 supplyCap = 1_000_000_000e18;
                      // After that the inflation is 2% per year as defined by the OLAS contract
                      uint256 maxMintCapFraction = 2;
                      // Get the supply cap until the year before the current year
                      for (uint256 i = 1; i < numYears; ++i) {
                          supplyCap += (supplyCap * maxMintCapFraction) / 100;
                      }
                      // Inflation amount is the difference between last two caps (inflation for the current year)
                      inflationAmount = (supplyCap * maxMintCapFraction) / 100;
                  }
              }
          }
          

          File 6 of 7: ComponentRegistry
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "./UnitRegistry.sol";
          /// @title Component Registry - Smart contract for registering components
          /// @author Aleksandr Kuperman - <[email protected]>
          contract ComponentRegistry is UnitRegistry {
              // Component registry version number
              string public constant VERSION = "1.0.0";
              /// @dev Component registry constructor.
              /// @param _name Component registry contract name.
              /// @param _symbol Component registry contract symbol.
              /// @param _baseURI Component registry token base URI.
              constructor(string memory _name, string memory _symbol, string memory _baseURI)
                  UnitRegistry(UnitType.Component)
                  ERC721(_name, _symbol)
              {
                  baseURI = _baseURI;
                  owner = msg.sender;
              }
              /// @dev Checks provided component dependencies.
              /// @param dependencies Set of component dependencies.
              /// @param maxComponentId Maximum component Id.
              function _checkDependencies(uint32[] memory dependencies, uint32 maxComponentId) internal virtual override {
                  uint32 lastId;
                  for (uint256 iDep = 0; iDep < dependencies.length; ++iDep) {
                      if (dependencies[iDep] < (lastId + 1) || dependencies[iDep] > maxComponentId) {
                          revert ComponentNotFound(dependencies[iDep]);
                      }
                      lastId = dependencies[iDep];
                  }
              }
              /// @dev Gets subcomponents of a provided component Id.
              /// @notice For components this means getting the linearized map of components from the local map of subcomponents.
              /// @param componentId Component Id.
              /// @return subComponentIds Set of subcomponents.
              function _getSubComponents(UnitType, uint32 componentId) internal view virtual override
                  returns (uint32[] memory subComponentIds)
              {
                  subComponentIds = mapSubComponents[uint256(componentId)];
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "./GenericRegistry.sol";
          /// @title Unit Registry - Smart contract for registering generalized units / units
          /// @author Aleksandr Kuperman - <[email protected]>
          abstract contract UnitRegistry is GenericRegistry {
              event CreateUnit(uint256 unitId, UnitType uType, bytes32 unitHash);
              event UpdateUnitHash(uint256 unitId, UnitType uType, bytes32 unitHash);
              enum UnitType {
                  Component,
                  Agent
              }
              // Unit parameters
              struct Unit {
                  // Primary IPFS hash of the unit
                  bytes32 unitHash;
                  // Set of component dependencies (agents are also based on components)
                  // We assume that the system is expected to support no more than 2^32-1 components
                  uint32[] dependencies;
              }
              // Type of the unit: component or unit
              UnitType public immutable unitType;
              // Map of unit Id => set of updated IPFS hashes
              mapping(uint256 => bytes32[]) public mapUnitIdHashes;
              // Map of unit Id => set of subcomponents (possible to derive from any registry)
              mapping(uint256 => uint32[]) public mapSubComponents;
              // Map of unit Id => unit
              mapping(uint256 => Unit) public mapUnits;
              constructor(UnitType _unitType) {
                  unitType = _unitType;
              }
              /// @dev Checks the provided component dependencies.
              /// @param dependencies Set of component dependencies.
              /// @param maxUnitId Maximum unit Id.
              function _checkDependencies(uint32[] memory dependencies, uint32 maxUnitId) internal virtual;
              /// @dev Creates unit.
              /// @param unitOwner Owner of the unit.
              /// @param unitHash IPFS CID hash of the unit.
              /// @param dependencies Set of unit dependencies in a sorted ascending order (unit Ids).
              /// @return unitId The id of a minted unit.
              function create(address unitOwner, bytes32 unitHash, uint32[] memory dependencies)
                  external virtual returns (uint256 unitId)
              {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  // Check for the manager privilege for a unit creation
                  if (manager != msg.sender) {
                      revert ManagerOnly(msg.sender, manager);
                  }
                  // Checks for a non-zero owner address
                  if(unitOwner == address(0)) {
                      revert ZeroAddress();
                  }
                  // Check for the non-zero hash value
                  if (unitHash == 0) {
                      revert ZeroValue();
                  }
                  
                  // Check for dependencies validity: must be already allocated, must not repeat
                  unitId = totalSupply;
                  _checkDependencies(dependencies, uint32(unitId));
                  // Unit with Id = 0 is left empty not to do additional checks for the index zero
                  unitId++;
                  // Initialize the unit and mint its token
                  Unit storage unit = mapUnits[unitId];
                  unit.unitHash = unitHash;
                  unit.dependencies = dependencies;
                  // Update the map of subcomponents with calculated subcomponents for the new unit Id
                  // In order to get the correct set of subcomponents, we need to differentiate between the callers of this function
                  // Self contract (unit registry) can only call subcomponents calculation from the component level
                  uint32[] memory subComponentIds = _calculateSubComponents(UnitType.Component, dependencies);
                  // We need to add a current component Id to the set of subcomponents if the unit is a component
                  // For example, if component 3 (c3) has dependencies of [c1, c2], then the subcomponents will return [c1, c2].
                  // The resulting set will be [c1, c2, c3]. So we write into the map of component subcomponents: c3=>[c1, c2, c3].
                  // This is done such that the subcomponents start getting explored, and when the agent calls its subcomponents,
                  // it would have [c1, c2, c3] right away instead of adding c3 manually and then (for services) checking
                  // if another agent also has c3 as a component dependency. The latter will consume additional computation.
                  if (unitType == UnitType.Component) {
                      uint256 numSubComponents = subComponentIds.length;
                      uint32[] memory addSubComponentIds = new uint32[](numSubComponents + 1);
                      for (uint256 i = 0; i < numSubComponents; ++i) {
                          addSubComponentIds[i] = subComponentIds[i];
                      }
                      // Adding self component Id
                      addSubComponentIds[numSubComponents] = uint32(unitId);
                      subComponentIds = addSubComponentIds;
                  }
                  mapSubComponents[unitId] = subComponentIds;
                  // Set total supply to the unit Id number
                  totalSupply = unitId;
                  // Safe mint is needed since contracts can create units as well
                  _safeMint(unitOwner, unitId);
                  emit CreateUnit(unitId, unitType, unitHash);
                  _locked = 1;
              }
              /// @dev Updates the unit hash.
              /// @param unitOwner Owner of the unit.
              /// @param unitId Unit Id.
              /// @param unitHash Updated IPFS hash of the unit.
              /// @return success True, if function executed successfully.
              function updateHash(address unitOwner, uint256 unitId, bytes32 unitHash) external virtual
                  returns (bool success)
              {
                  // Check the manager privilege for a unit modification
                  if (manager != msg.sender) {
                      revert ManagerOnly(msg.sender, manager);
                  }
                  // Checking the unit ownership
                  if (ownerOf(unitId) != unitOwner) {
                      if (unitType == UnitType.Component) {
                          revert ComponentNotFound(unitId);
                      } else {
                          revert AgentNotFound(unitId);
                      }
                  }
                  // Check for the hash value
                  if (unitHash == 0) {
                      revert ZeroValue();
                  }
                  mapUnitIdHashes[unitId].push(unitHash);
                  success = true;
                  emit UpdateUnitHash(unitId, unitType, unitHash);
              }
              /// @dev Gets the unit instance.
              /// @param unitId Unit Id.
              /// @return unit Corresponding Unit struct.
              function getUnit(uint256 unitId) external view virtual returns (Unit memory unit) {
                  unit = mapUnits[unitId];
              }
              /// @dev Gets unit dependencies.
              /// @param unitId Unit Id.
              /// @return numDependencies The number of units in the dependency list.
              /// @return dependencies The list of unit dependencies.
              function getDependencies(uint256 unitId) external view virtual
                  returns (uint256 numDependencies, uint32[] memory dependencies)
              {
                  Unit memory unit = mapUnits[unitId];
                  return (unit.dependencies.length, unit.dependencies);
              }
              /// @dev Gets updated unit hashes.
              /// @param unitId Unit Id.
              /// @return numHashes Number of hashes.
              /// @return unitHashes The list of updated unit hashes (without the primary one).
              function getUpdatedHashes(uint256 unitId) external view virtual
                  returns (uint256 numHashes, bytes32[] memory unitHashes)
              {
                  unitHashes = mapUnitIdHashes[unitId];
                  return (unitHashes.length, unitHashes);
              }
              /// @dev Gets the set of subcomponent Ids from a local map of subcomponent.
              /// @param unitId Component Id.
              /// @return subComponentIds Set of subcomponent Ids.
              /// @return numSubComponents Number of subcomponents.
              function getLocalSubComponents(uint256 unitId) external view
                  returns (uint32[] memory subComponentIds, uint256 numSubComponents)
              {
                  subComponentIds = mapSubComponents[uint256(unitId)];
                  numSubComponents = subComponentIds.length;
              }
              /// @dev Gets subcomponents of a provided unit Id.
              /// @param subcomponentsFromType Type of the unit: component or agent.
              /// @param unitId Unit Id.
              /// @return subComponentIds Set of subcomponents.
              function _getSubComponents(UnitType subcomponentsFromType, uint32 unitId) internal view virtual
                  returns (uint32[] memory subComponentIds);
              /// @dev Calculates the set of subcomponent Ids.
              /// @param subcomponentsFromType Type of the unit: component or agent.
              /// @param unitIds Unit Ids.
              /// @return subComponentIds Subcomponent Ids.
              function _calculateSubComponents(UnitType subcomponentsFromType, uint32[] memory unitIds) internal view virtual
                  returns (uint32[] memory subComponentIds)
              {
                  uint32 numUnits = uint32(unitIds.length);
                  // Array of numbers of components per each unit Id
                  uint32[] memory numComponents = new uint32[](numUnits);
                  // 2D array of all the sets of components per each unit Id
                  uint32[][] memory components = new uint32[][](numUnits);
                  // Get total possible number of components and lists of components
                  uint32 maxNumComponents;
                  for (uint32 i = 0; i < numUnits; ++i) {
                      // Get subcomponents for each unit Id based on the subcomponentsFromType
                      components[i] = _getSubComponents(subcomponentsFromType, unitIds[i]);
                      numComponents[i] = uint32(components[i].length);
                      maxNumComponents += numComponents[i];
                  }
                  // Lists of components are sorted, take unique values in ascending order
                  uint32[] memory allComponents = new uint32[](maxNumComponents);
                  // Processed component counter
                  uint32[] memory processedComponents = new uint32[](numUnits);
                  // Minimal component Id
                  uint32 minComponent;
                  // Overall component counter
                  uint32 counter;
                  // Iterate until we process all components, at the maximum of the sum of all the components in all units
                  for (counter = 0; counter < maxNumComponents; ++counter) {
                      // Index of a minimal component
                      uint32 minIdxComponent;
                      // Amount of components identified as the next minimal component number
                      uint32 numComponentsCheck;
                      uint32 tryMinComponent = type(uint32).max;
                      // Assemble an array of all first components from each component array
                      for (uint32 i = 0; i < numUnits; ++i) {
                          // Either get a component that has a higher id than the last one ore reach the end of the processed Ids
                          for (; processedComponents[i] < numComponents[i]; ++processedComponents[i]) {
                              if (minComponent < components[i][processedComponents[i]]) {
                                  // Out of those component Ids that are higher than the last one, pick the minimal one
                                  if (components[i][processedComponents[i]] < tryMinComponent) {
                                      tryMinComponent = components[i][processedComponents[i]];
                                      minIdxComponent = i;
                                  }
                                  // If we found a minimal component Id, we increase the counter and break to start the search again
                                  numComponentsCheck++;
                                  break;
                              }
                          }
                      }
                      minComponent = tryMinComponent;
                      // If minimal component Id is greater than the last one, it should be added, otherwise we reached the end
                      if (numComponentsCheck > 0) {
                          allComponents[counter] = minComponent;
                          processedComponents[minIdxComponent]++;
                      } else {
                          break;
                      }
                  }
                  // Return the exact set of found subcomponents with the counter length
                  subComponentIds = new uint32[](counter);
                  for (uint32 i = 0; i < counter; ++i) {
                      subComponentIds[i] = allComponents[i];
                  }
              }
              /// @dev Gets the hash of the unit.
              /// @param unitId Unit Id.
              /// @return Unit hash.
              function _getUnitHash(uint256 unitId) internal view override returns (bytes32) {
                  return mapUnits[unitId].unitHash;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "../lib/solmate/src/tokens/ERC721.sol";
          import "./interfaces/IErrorsRegistries.sol";
          /// @title Generic Registry - Smart contract for generic registry template
          /// @author Aleksandr Kuperman - <[email protected]>
          abstract contract GenericRegistry is IErrorsRegistries, ERC721 {
              event OwnerUpdated(address indexed owner);
              event ManagerUpdated(address indexed manager);
              event BaseURIChanged(string baseURI);
              // Owner address
              address public owner;
              // Unit manager
              address public manager;
              // Base URI
              string public baseURI;
              // Unit counter
              uint256 public totalSupply;
              // Reentrancy lock
              uint256 internal _locked = 1;
              // To better understand the CID anatomy, please refer to: https://proto.school/anatomy-of-a-cid/05
              // CID = <multibase_encoding>multibase_encoding(<cid-version><multicodec><multihash-algorithm><multihash-length><multihash-hash>)
              // CID prefix = <multibase_encoding>multibase_encoding(<cid-version><multicodec><multihash-algorithm><multihash-length>)
              // to complement the multibase_encoding(<multihash-hash>)
              // multibase_encoding = base16 = "f"
              // cid-version = version 1 = "0x01"
              // multicodec = dag-pb = "0x70"
              // multihash-algorithm = sha2-256 = "0x12"
              // multihash-length = 256 bits = "0x20"
              string public constant CID_PREFIX = "f01701220";
              /// @dev Changes the owner address.
              /// @param newOwner Address of a new owner.
              function changeOwner(address newOwner) external virtual {
                  // Check for the 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 the unit manager.
              /// @param newManager Address of a new unit manager.
              function changeManager(address newManager) external virtual {
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for the zero address
                  if (newManager == address(0)) {
                      revert ZeroAddress();
                  }
                  manager = newManager;
                  emit ManagerUpdated(newManager);
              }
              /// @dev Checks for the unit existence.
              /// @notice Unit counter starts from 1.
              /// @param unitId Unit Id.
              /// @return true if the unit exists, false otherwise.
              function exists(uint256 unitId) external view virtual returns (bool) {
                  return unitId > 0 && unitId < (totalSupply + 1);
              }
              
              /// @dev Sets unit base URI.
              /// @param bURI Base URI string.
              function setBaseURI(string memory bURI) external virtual {
                  // Check for the ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for the zero value
                  if (bytes(bURI).length == 0) {
                      revert ZeroValue();
                  }
                  baseURI = bURI;
                  emit BaseURIChanged(bURI);
              }
              /// @dev Gets the valid unit Id from the provided index.
              /// @notice Unit counter starts from 1.
              /// @param id Unit counter.
              /// @return unitId Unit Id.
              function tokenByIndex(uint256 id) external view virtual returns (uint256 unitId) {
                  unitId = id + 1;
                  if (unitId > totalSupply) {
                      revert Overflow(unitId, totalSupply);
                  }
              }
              // Open sourced from: https://stackoverflow.com/questions/67893318/solidity-how-to-represent-bytes32-as-string
              /// @dev Converts bytes16 input data to hex16.
              /// @notice This method converts bytes into the same bytes-character hex16 representation.
              /// @param data bytes16 input data.
              /// @return result hex16 conversion from the input bytes16 data.
              function _toHex16(bytes16 data) internal pure returns (bytes32 result) {
                  result = bytes32 (data) & 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000 |
                  (bytes32 (data) & 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000) >> 64;
                  result = result & 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000 |
                  (result & 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000) >> 32;
                  result = result & 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000 |
                  (result & 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000) >> 16;
                  result = result & 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 |
                  (result & 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000) >> 8;
                  result = (result & 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000) >> 4 |
                  (result & 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00) >> 8;
                  result = bytes32 (0x3030303030303030303030303030303030303030303030303030303030303030 +
                  uint256 (result) +
                      (uint256 (result) + 0x0606060606060606060606060606060606060606060606060606060606060606 >> 4 &
                      0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) * 39);
              }
              /// @dev Gets the hash of the unit.
              /// @param unitId Unit Id.
              /// @return Unit hash.
              function _getUnitHash(uint256 unitId) internal view virtual returns (bytes32);
              /// @dev Returns unit token URI.
              /// @notice Expected multicodec: dag-pb; hashing function: sha2-256, with base16 encoding and leading CID_PREFIX removed.
              /// @param unitId Unit Id.
              /// @return Unit token URI string.
              function tokenURI(uint256 unitId) public view virtual override returns (string memory) {
                  bytes32 unitHash = _getUnitHash(unitId);
                  // Parse 2 parts of bytes32 into left and right hex16 representation, and concatenate into string
                  // adding the base URI and a cid prefix for the full base16 multibase prefix IPFS hash representation
                  return string(abi.encodePacked(baseURI, CID_PREFIX, _toHex16(bytes16(unitHash)),
                      _toHex16(bytes16(unitHash << 128))));
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.0;
          /// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
          /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
          abstract contract ERC721 {
              /*//////////////////////////////////////////////////////////////
                                           EVENTS
              //////////////////////////////////////////////////////////////*/
              event Transfer(address indexed from, address indexed to, uint256 indexed id);
              event Approval(address indexed owner, address indexed spender, uint256 indexed id);
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /*//////////////////////////////////////////////////////////////
                                   METADATA STORAGE/LOGIC
              //////////////////////////////////////////////////////////////*/
              string public name;
              string public symbol;
              function tokenURI(uint256 id) public view virtual returns (string memory);
              /*//////////////////////////////////////////////////////////////
                                ERC721 BALANCE/OWNER STORAGE
              //////////////////////////////////////////////////////////////*/
              mapping(uint256 => address) internal _ownerOf;
              mapping(address => uint256) internal _balanceOf;
              function ownerOf(uint256 id) public view virtual returns (address owner) {
                  require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
              }
              function balanceOf(address owner) public view virtual returns (uint256) {
                  require(owner != address(0), "ZERO_ADDRESS");
                  return _balanceOf[owner];
              }
              /*//////////////////////////////////////////////////////////////
                                   ERC721 APPROVAL STORAGE
              //////////////////////////////////////////////////////////////*/
              mapping(uint256 => address) public getApproved;
              mapping(address => mapping(address => bool)) public isApprovedForAll;
              /*//////////////////////////////////////////////////////////////
                                         CONSTRUCTOR
              //////////////////////////////////////////////////////////////*/
              constructor(string memory _name, string memory _symbol) {
                  name = _name;
                  symbol = _symbol;
              }
              /*//////////////////////////////////////////////////////////////
                                        ERC721 LOGIC
              //////////////////////////////////////////////////////////////*/
              function approve(address spender, uint256 id) public virtual {
                  address owner = _ownerOf[id];
                  require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
                  getApproved[id] = spender;
                  emit Approval(owner, spender, id);
              }
              function setApprovalForAll(address operator, bool approved) public virtual {
                  isApprovedForAll[msg.sender][operator] = approved;
                  emit ApprovalForAll(msg.sender, operator, approved);
              }
              function transferFrom(
                  address from,
                  address to,
                  uint256 id
              ) public virtual {
                  require(from == _ownerOf[id], "WRONG_FROM");
                  require(to != address(0), "INVALID_RECIPIENT");
                  require(
                      msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
                      "NOT_AUTHORIZED"
                  );
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  unchecked {
                      _balanceOf[from]--;
                      _balanceOf[to]++;
                  }
                  _ownerOf[id] = to;
                  delete getApproved[id];
                  emit Transfer(from, to, id);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id
              ) public virtual {
                  transferFrom(from, to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id,
                  bytes calldata data
              ) public virtual {
                  transferFrom(from, to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
              /*//////////////////////////////////////////////////////////////
                                        ERC165 LOGIC
              //////////////////////////////////////////////////////////////*/
              function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
                  return
                      interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
                      interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
                      interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
              }
              /*//////////////////////////////////////////////////////////////
                                  INTERNAL MINT/BURN LOGIC
              //////////////////////////////////////////////////////////////*/
              function _mint(address to, uint256 id) internal virtual {
                  require(to != address(0), "INVALID_RECIPIENT");
                  require(_ownerOf[id] == address(0), "ALREADY_MINTED");
                  // Counter overflow is incredibly unrealistic.
                  unchecked {
                      _balanceOf[to]++;
                  }
                  _ownerOf[id] = to;
                  emit Transfer(address(0), to, id);
              }
              function _burn(uint256 id) internal virtual {
                  address owner = _ownerOf[id];
                  require(owner != address(0), "NOT_MINTED");
                  // Ownership check above ensures no underflow.
                  unchecked {
                      _balanceOf[owner]--;
                  }
                  delete _ownerOf[id];
                  delete getApproved[id];
                  emit Transfer(owner, address(0), id);
              }
              /*//////////////////////////////////////////////////////////////
                                  INTERNAL SAFE MINT LOGIC
              //////////////////////////////////////////////////////////////*/
              function _safeMint(address to, uint256 id) internal virtual {
                  _mint(to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
              function _safeMint(
                  address to,
                  uint256 id,
                  bytes memory data
              ) internal virtual {
                  _mint(to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
          }
          /// @notice A generic interface for a contract which properly accepts ERC721 tokens.
          /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
          abstract contract ERC721TokenReceiver {
              function onERC721Received(
                  address,
                  address,
                  uint256,
                  bytes calldata
              ) external virtual returns (bytes4) {
                  return ERC721TokenReceiver.onERC721Received.selector;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          /// @dev Errors.
          interface IErrorsRegistries {
              /// @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 Hash already exists in the records.
              error HashExists();
              /// @dev Provided zero address.
              error ZeroAddress();
              /// @dev Agent Id is not correctly provided for the current routine.
              /// @param agentId Component Id.
              error WrongAgentId(uint256 agentId);
              /// @dev Wrong length of two arrays.
              /// @param numValues1 Number of values in a first array.
              /// @param numValues2 Numberf of values in a second array.
              error WrongArrayLength(uint256 numValues1, uint256 numValues2);
              /// @dev Canonical agent Id is not found.
              /// @param agentId Canonical agent Id.
              error AgentNotFound(uint256 agentId);
              /// @dev Component Id is not found.
              /// @param componentId Component Id.
              error ComponentNotFound(uint256 componentId);
              /// @dev Multisig threshold is out of bounds.
              /// @param currentThreshold Current threshold value.
              /// @param minThreshold Minimum possible threshold value.
              /// @param maxThreshold Maximum possible threshold value.
              error WrongThreshold(uint256 currentThreshold, uint256 minThreshold, uint256 maxThreshold);
              /// @dev Agent instance is already registered with a specified `operator`.
              /// @param operator Operator that registered an instance.
              error AgentInstanceRegistered(address operator);
              /// @dev Wrong operator is specified when interacting with a specified `serviceId`.
              /// @param serviceId Service Id.
              error WrongOperator(uint256 serviceId);
              /// @dev Operator has no registered instances in the service.
              /// @param operator Operator address.
              /// @param serviceId Service Id.
              error OperatorHasNoInstances(address operator, uint256 serviceId);
              /// @dev Canonical `agentId` is not found as a part of `serviceId`.
              /// @param agentId Canonical agent Id.
              /// @param serviceId Service Id.
              error AgentNotInService(uint256 agentId, uint256 serviceId);
              /// @dev The contract is paused.
              error Paused();
              /// @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 must be inactive.
              /// @param serviceId Service Id.
              error ServiceMustBeInactive(uint256 serviceId);
              /// @dev All the agent instance slots for a specific `serviceId` are filled.
              /// @param serviceId Service Id.
              error AgentInstancesSlotsFilled(uint256 serviceId);
              /// @dev Wrong state of a service.
              /// @param state Service state.
              /// @param serviceId Service Id.
              error WrongServiceState(uint256 state, uint256 serviceId);
              /// @dev Only own service multisig is allowed.
              /// @param provided Provided address.
              /// @param expected Expected multisig address.
              /// @param serviceId Service Id.
              error OnlyOwnServiceMultisig(address provided, address expected, uint256 serviceId);
              /// @dev Multisig is not whitelisted.
              /// @param multisig Address of a multisig implementation.
              error UnauthorizedMultisig(address multisig);
              /// @dev Incorrect deposit provided for the registration activation.
              /// @param sent Sent amount.
              /// @param expected Expected amount.
              /// @param serviceId Service Id.
              error IncorrectRegistrationDepositValue(uint256 sent, uint256 expected, uint256 serviceId);
              /// @dev Insufficient value provided for the agent instance bonding.
              /// @param sent Sent amount.
              /// @param expected Expected amount.
              /// @param serviceId Service Id.
              error IncorrectAgentBondingValue(uint256 sent, uint256 expected, uint256 serviceId);
              /// @dev Failure of a transfer.
              /// @param token Address of a token.
              /// @param from Address `from`.
              /// @param to Address `to`.
              /// @param value Value.
              error TransferFailed(address token, address from, address to, uint256 value);
              /// @dev Caught reentrancy violation.
              error ReentrancyGuard();
          }
          

          File 7 of 7: AgentRegistry
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "./UnitRegistry.sol";
          import "./interfaces/IRegistry.sol";
          /// @title Agent Registry - Smart contract for registering agents
          /// @author Aleksandr Kuperman - <[email protected]>
          contract AgentRegistry is UnitRegistry {
              // Component registry
              address public immutable componentRegistry;
              // Agent registry version number
              string public constant VERSION = "1.0.0";
              /// @dev Agent registry constructor.
              /// @param _name Agent registry contract name.
              /// @param _symbol Agent registry contract symbol.
              /// @param _baseURI Agent registry token base URI.
              /// @param _componentRegistry Component registry address.
              constructor(string memory _name, string memory _symbol, string memory _baseURI, address _componentRegistry)
                  UnitRegistry(UnitType.Agent)
                  ERC721(_name, _symbol)
              {
                  baseURI = _baseURI;
                  componentRegistry = _componentRegistry;
                  owner = msg.sender;
              }
              /// @dev Checks provided component dependencies.
              /// @param dependencies Set of component dependencies.
              function _checkDependencies(uint32[] memory dependencies, uint32) internal virtual override {
                  // Check that the agent has at least one component
                  if (dependencies.length == 0) {
                      revert ZeroValue();
                  }
                  // Get the components total supply
                  uint32 componentTotalSupply = uint32(IRegistry(componentRegistry).totalSupply());
                  uint32 lastId;
                  for (uint256 iDep = 0; iDep < dependencies.length; ++iDep) {
                      if (dependencies[iDep] < (lastId + 1) || dependencies[iDep] > componentTotalSupply) {
                          revert ComponentNotFound(dependencies[iDep]);
                      }
                      lastId = dependencies[iDep];
                  }
              }
              /// @dev Gets linearized set of subcomponents of a provided unit Id and a type of a component.
              /// @notice (0) For components this means getting the linearized map of components from the componentRegistry contract.
              /// @notice (1) For agents this means getting the linearized map of components from the local map of subcomponents.
              /// @param subcomponentsFromType Type of the unit: component or agent.
              /// @param unitId Component Id.
              /// @return subComponentIds Set of subcomponents.
              function _getSubComponents(UnitType subcomponentsFromType, uint32 unitId) internal view virtual override
                  returns (uint32[] memory subComponentIds)
              {
                  // Self contract (agent registry) can only call subcomponents calculation from the component level (0)
                  // Otherwise, the subcomponents are already written into the corresponding subcomponents map
                  if (subcomponentsFromType == UnitType.Component) {
                      (subComponentIds, ) = IRegistry(componentRegistry).getLocalSubComponents(uint256(unitId));
                  } else {
                      subComponentIds = mapSubComponents[uint256(unitId)];
                  }
              }
              /// @dev Calculates the set of subcomponent Ids.
              /// @notice We assume that the external callers calculate subcomponents from the higher unit hierarchy level: agents.
              /// @param unitIds Unit Ids.
              /// @return subComponentIds Subcomponent Ids.
              function calculateSubComponents(uint32[] memory unitIds) external view returns (uint32[] memory subComponentIds)
              {
                  subComponentIds = _calculateSubComponents(UnitType.Agent, unitIds);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "./GenericRegistry.sol";
          /// @title Unit Registry - Smart contract for registering generalized units / units
          /// @author Aleksandr Kuperman - <[email protected]>
          abstract contract UnitRegistry is GenericRegistry {
              event CreateUnit(uint256 unitId, UnitType uType, bytes32 unitHash);
              event UpdateUnitHash(uint256 unitId, UnitType uType, bytes32 unitHash);
              enum UnitType {
                  Component,
                  Agent
              }
              // Unit parameters
              struct Unit {
                  // Primary IPFS hash of the unit
                  bytes32 unitHash;
                  // Set of component dependencies (agents are also based on components)
                  // We assume that the system is expected to support no more than 2^32-1 components
                  uint32[] dependencies;
              }
              // Type of the unit: component or unit
              UnitType public immutable unitType;
              // Map of unit Id => set of updated IPFS hashes
              mapping(uint256 => bytes32[]) public mapUnitIdHashes;
              // Map of unit Id => set of subcomponents (possible to derive from any registry)
              mapping(uint256 => uint32[]) public mapSubComponents;
              // Map of unit Id => unit
              mapping(uint256 => Unit) public mapUnits;
              constructor(UnitType _unitType) {
                  unitType = _unitType;
              }
              /// @dev Checks the provided component dependencies.
              /// @param dependencies Set of component dependencies.
              /// @param maxUnitId Maximum unit Id.
              function _checkDependencies(uint32[] memory dependencies, uint32 maxUnitId) internal virtual;
              /// @dev Creates unit.
              /// @param unitOwner Owner of the unit.
              /// @param unitHash IPFS CID hash of the unit.
              /// @param dependencies Set of unit dependencies in a sorted ascending order (unit Ids).
              /// @return unitId The id of a minted unit.
              function create(address unitOwner, bytes32 unitHash, uint32[] memory dependencies)
                  external virtual returns (uint256 unitId)
              {
                  // Reentrancy guard
                  if (_locked > 1) {
                      revert ReentrancyGuard();
                  }
                  _locked = 2;
                  // Check for the manager privilege for a unit creation
                  if (manager != msg.sender) {
                      revert ManagerOnly(msg.sender, manager);
                  }
                  // Checks for a non-zero owner address
                  if(unitOwner == address(0)) {
                      revert ZeroAddress();
                  }
                  // Check for the non-zero hash value
                  if (unitHash == 0) {
                      revert ZeroValue();
                  }
                  
                  // Check for dependencies validity: must be already allocated, must not repeat
                  unitId = totalSupply;
                  _checkDependencies(dependencies, uint32(unitId));
                  // Unit with Id = 0 is left empty not to do additional checks for the index zero
                  unitId++;
                  // Initialize the unit and mint its token
                  Unit storage unit = mapUnits[unitId];
                  unit.unitHash = unitHash;
                  unit.dependencies = dependencies;
                  // Update the map of subcomponents with calculated subcomponents for the new unit Id
                  // In order to get the correct set of subcomponents, we need to differentiate between the callers of this function
                  // Self contract (unit registry) can only call subcomponents calculation from the component level
                  uint32[] memory subComponentIds = _calculateSubComponents(UnitType.Component, dependencies);
                  // We need to add a current component Id to the set of subcomponents if the unit is a component
                  // For example, if component 3 (c3) has dependencies of [c1, c2], then the subcomponents will return [c1, c2].
                  // The resulting set will be [c1, c2, c3]. So we write into the map of component subcomponents: c3=>[c1, c2, c3].
                  // This is done such that the subcomponents start getting explored, and when the agent calls its subcomponents,
                  // it would have [c1, c2, c3] right away instead of adding c3 manually and then (for services) checking
                  // if another agent also has c3 as a component dependency. The latter will consume additional computation.
                  if (unitType == UnitType.Component) {
                      uint256 numSubComponents = subComponentIds.length;
                      uint32[] memory addSubComponentIds = new uint32[](numSubComponents + 1);
                      for (uint256 i = 0; i < numSubComponents; ++i) {
                          addSubComponentIds[i] = subComponentIds[i];
                      }
                      // Adding self component Id
                      addSubComponentIds[numSubComponents] = uint32(unitId);
                      subComponentIds = addSubComponentIds;
                  }
                  mapSubComponents[unitId] = subComponentIds;
                  // Set total supply to the unit Id number
                  totalSupply = unitId;
                  // Safe mint is needed since contracts can create units as well
                  _safeMint(unitOwner, unitId);
                  emit CreateUnit(unitId, unitType, unitHash);
                  _locked = 1;
              }
              /// @dev Updates the unit hash.
              /// @param unitOwner Owner of the unit.
              /// @param unitId Unit Id.
              /// @param unitHash Updated IPFS hash of the unit.
              /// @return success True, if function executed successfully.
              function updateHash(address unitOwner, uint256 unitId, bytes32 unitHash) external virtual
                  returns (bool success)
              {
                  // Check the manager privilege for a unit modification
                  if (manager != msg.sender) {
                      revert ManagerOnly(msg.sender, manager);
                  }
                  // Checking the unit ownership
                  if (ownerOf(unitId) != unitOwner) {
                      if (unitType == UnitType.Component) {
                          revert ComponentNotFound(unitId);
                      } else {
                          revert AgentNotFound(unitId);
                      }
                  }
                  // Check for the hash value
                  if (unitHash == 0) {
                      revert ZeroValue();
                  }
                  mapUnitIdHashes[unitId].push(unitHash);
                  success = true;
                  emit UpdateUnitHash(unitId, unitType, unitHash);
              }
              /// @dev Gets the unit instance.
              /// @param unitId Unit Id.
              /// @return unit Corresponding Unit struct.
              function getUnit(uint256 unitId) external view virtual returns (Unit memory unit) {
                  unit = mapUnits[unitId];
              }
              /// @dev Gets unit dependencies.
              /// @param unitId Unit Id.
              /// @return numDependencies The number of units in the dependency list.
              /// @return dependencies The list of unit dependencies.
              function getDependencies(uint256 unitId) external view virtual
                  returns (uint256 numDependencies, uint32[] memory dependencies)
              {
                  Unit memory unit = mapUnits[unitId];
                  return (unit.dependencies.length, unit.dependencies);
              }
              /// @dev Gets updated unit hashes.
              /// @param unitId Unit Id.
              /// @return numHashes Number of hashes.
              /// @return unitHashes The list of updated unit hashes (without the primary one).
              function getUpdatedHashes(uint256 unitId) external view virtual
                  returns (uint256 numHashes, bytes32[] memory unitHashes)
              {
                  unitHashes = mapUnitIdHashes[unitId];
                  return (unitHashes.length, unitHashes);
              }
              /// @dev Gets the set of subcomponent Ids from a local map of subcomponent.
              /// @param unitId Component Id.
              /// @return subComponentIds Set of subcomponent Ids.
              /// @return numSubComponents Number of subcomponents.
              function getLocalSubComponents(uint256 unitId) external view
                  returns (uint32[] memory subComponentIds, uint256 numSubComponents)
              {
                  subComponentIds = mapSubComponents[uint256(unitId)];
                  numSubComponents = subComponentIds.length;
              }
              /// @dev Gets subcomponents of a provided unit Id.
              /// @param subcomponentsFromType Type of the unit: component or agent.
              /// @param unitId Unit Id.
              /// @return subComponentIds Set of subcomponents.
              function _getSubComponents(UnitType subcomponentsFromType, uint32 unitId) internal view virtual
                  returns (uint32[] memory subComponentIds);
              /// @dev Calculates the set of subcomponent Ids.
              /// @param subcomponentsFromType Type of the unit: component or agent.
              /// @param unitIds Unit Ids.
              /// @return subComponentIds Subcomponent Ids.
              function _calculateSubComponents(UnitType subcomponentsFromType, uint32[] memory unitIds) internal view virtual
                  returns (uint32[] memory subComponentIds)
              {
                  uint32 numUnits = uint32(unitIds.length);
                  // Array of numbers of components per each unit Id
                  uint32[] memory numComponents = new uint32[](numUnits);
                  // 2D array of all the sets of components per each unit Id
                  uint32[][] memory components = new uint32[][](numUnits);
                  // Get total possible number of components and lists of components
                  uint32 maxNumComponents;
                  for (uint32 i = 0; i < numUnits; ++i) {
                      // Get subcomponents for each unit Id based on the subcomponentsFromType
                      components[i] = _getSubComponents(subcomponentsFromType, unitIds[i]);
                      numComponents[i] = uint32(components[i].length);
                      maxNumComponents += numComponents[i];
                  }
                  // Lists of components are sorted, take unique values in ascending order
                  uint32[] memory allComponents = new uint32[](maxNumComponents);
                  // Processed component counter
                  uint32[] memory processedComponents = new uint32[](numUnits);
                  // Minimal component Id
                  uint32 minComponent;
                  // Overall component counter
                  uint32 counter;
                  // Iterate until we process all components, at the maximum of the sum of all the components in all units
                  for (counter = 0; counter < maxNumComponents; ++counter) {
                      // Index of a minimal component
                      uint32 minIdxComponent;
                      // Amount of components identified as the next minimal component number
                      uint32 numComponentsCheck;
                      uint32 tryMinComponent = type(uint32).max;
                      // Assemble an array of all first components from each component array
                      for (uint32 i = 0; i < numUnits; ++i) {
                          // Either get a component that has a higher id than the last one ore reach the end of the processed Ids
                          for (; processedComponents[i] < numComponents[i]; ++processedComponents[i]) {
                              if (minComponent < components[i][processedComponents[i]]) {
                                  // Out of those component Ids that are higher than the last one, pick the minimal one
                                  if (components[i][processedComponents[i]] < tryMinComponent) {
                                      tryMinComponent = components[i][processedComponents[i]];
                                      minIdxComponent = i;
                                  }
                                  // If we found a minimal component Id, we increase the counter and break to start the search again
                                  numComponentsCheck++;
                                  break;
                              }
                          }
                      }
                      minComponent = tryMinComponent;
                      // If minimal component Id is greater than the last one, it should be added, otherwise we reached the end
                      if (numComponentsCheck > 0) {
                          allComponents[counter] = minComponent;
                          processedComponents[minIdxComponent]++;
                      } else {
                          break;
                      }
                  }
                  // Return the exact set of found subcomponents with the counter length
                  subComponentIds = new uint32[](counter);
                  for (uint32 i = 0; i < counter; ++i) {
                      subComponentIds[i] = allComponents[i];
                  }
              }
              /// @dev Gets the hash of the unit.
              /// @param unitId Unit Id.
              /// @return Unit hash.
              function _getUnitHash(uint256 unitId) internal view override returns (bytes32) {
                  return mapUnits[unitId].unitHash;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          /// @dev Required interface for the component / agent manipulation.
          interface IRegistry {
              enum UnitType {
                  Component,
                  Agent
              }
              /// @dev Creates component / agent.
              /// @param unitOwner Owner of the component / agent.
              /// @param unitHash IPFS hash of the component / agent.
              /// @param dependencies Set of component dependencies in a sorted ascending order.
              /// @return The id of a minted component / agent.
              function create(
                  address unitOwner,
                  bytes32 unitHash,
                  uint32[] memory dependencies
              ) external returns (uint256);
              /// @dev Updates the component / agent hash.
              /// @param owner Owner of the component / agent.
              /// @param unitId Unit Id.
              /// @param unitHash Updated IPFS hash of the component / agent.
              /// @return success True, if function executed successfully.
              function updateHash(address owner, uint256 unitId, bytes32 unitHash) external returns (bool success);
              /// @dev Gets subcomponents of a provided unit Id from a local public map.
              /// @param unitId Unit Id.
              /// @return subComponentIds Set of subcomponents.
              /// @return numSubComponents Number of subcomponents.
              function getLocalSubComponents(uint256 unitId) external view returns (uint32[] memory subComponentIds, uint256 numSubComponents);
              /// @dev Calculates the set of subcomponent Ids.
              /// @param unitIds Set of unit Ids.
              /// @return subComponentIds Subcomponent Ids.
              function calculateSubComponents(uint32[] memory unitIds) external view returns (uint32[] memory subComponentIds);
              /// @dev Gets updated component / agent hashes.
              /// @param unitId Unit Id.
              /// @return numHashes Number of hashes.
              /// @return unitHashes The list of component / agent hashes.
              function getUpdatedHashes(uint256 unitId) external view returns (uint256 numHashes, bytes32[] memory unitHashes);
              /// @dev Gets the total supply of components / agents.
              /// @return Total supply.
              function totalSupply() external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          import "../lib/solmate/src/tokens/ERC721.sol";
          import "./interfaces/IErrorsRegistries.sol";
          /// @title Generic Registry - Smart contract for generic registry template
          /// @author Aleksandr Kuperman - <[email protected]>
          abstract contract GenericRegistry is IErrorsRegistries, ERC721 {
              event OwnerUpdated(address indexed owner);
              event ManagerUpdated(address indexed manager);
              event BaseURIChanged(string baseURI);
              // Owner address
              address public owner;
              // Unit manager
              address public manager;
              // Base URI
              string public baseURI;
              // Unit counter
              uint256 public totalSupply;
              // Reentrancy lock
              uint256 internal _locked = 1;
              // To better understand the CID anatomy, please refer to: https://proto.school/anatomy-of-a-cid/05
              // CID = <multibase_encoding>multibase_encoding(<cid-version><multicodec><multihash-algorithm><multihash-length><multihash-hash>)
              // CID prefix = <multibase_encoding>multibase_encoding(<cid-version><multicodec><multihash-algorithm><multihash-length>)
              // to complement the multibase_encoding(<multihash-hash>)
              // multibase_encoding = base16 = "f"
              // cid-version = version 1 = "0x01"
              // multicodec = dag-pb = "0x70"
              // multihash-algorithm = sha2-256 = "0x12"
              // multihash-length = 256 bits = "0x20"
              string public constant CID_PREFIX = "f01701220";
              /// @dev Changes the owner address.
              /// @param newOwner Address of a new owner.
              function changeOwner(address newOwner) external virtual {
                  // Check for the 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 the unit manager.
              /// @param newManager Address of a new unit manager.
              function changeManager(address newManager) external virtual {
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for the zero address
                  if (newManager == address(0)) {
                      revert ZeroAddress();
                  }
                  manager = newManager;
                  emit ManagerUpdated(newManager);
              }
              /// @dev Checks for the unit existence.
              /// @notice Unit counter starts from 1.
              /// @param unitId Unit Id.
              /// @return true if the unit exists, false otherwise.
              function exists(uint256 unitId) external view virtual returns (bool) {
                  return unitId > 0 && unitId < (totalSupply + 1);
              }
              
              /// @dev Sets unit base URI.
              /// @param bURI Base URI string.
              function setBaseURI(string memory bURI) external virtual {
                  // Check for the ownership
                  if (msg.sender != owner) {
                      revert OwnerOnly(msg.sender, owner);
                  }
                  // Check for the zero value
                  if (bytes(bURI).length == 0) {
                      revert ZeroValue();
                  }
                  baseURI = bURI;
                  emit BaseURIChanged(bURI);
              }
              /// @dev Gets the valid unit Id from the provided index.
              /// @notice Unit counter starts from 1.
              /// @param id Unit counter.
              /// @return unitId Unit Id.
              function tokenByIndex(uint256 id) external view virtual returns (uint256 unitId) {
                  unitId = id + 1;
                  if (unitId > totalSupply) {
                      revert Overflow(unitId, totalSupply);
                  }
              }
              // Open sourced from: https://stackoverflow.com/questions/67893318/solidity-how-to-represent-bytes32-as-string
              /// @dev Converts bytes16 input data to hex16.
              /// @notice This method converts bytes into the same bytes-character hex16 representation.
              /// @param data bytes16 input data.
              /// @return result hex16 conversion from the input bytes16 data.
              function _toHex16(bytes16 data) internal pure returns (bytes32 result) {
                  result = bytes32 (data) & 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000 |
                  (bytes32 (data) & 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000) >> 64;
                  result = result & 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000 |
                  (result & 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000) >> 32;
                  result = result & 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000 |
                  (result & 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000) >> 16;
                  result = result & 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 |
                  (result & 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000) >> 8;
                  result = (result & 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000) >> 4 |
                  (result & 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00) >> 8;
                  result = bytes32 (0x3030303030303030303030303030303030303030303030303030303030303030 +
                  uint256 (result) +
                      (uint256 (result) + 0x0606060606060606060606060606060606060606060606060606060606060606 >> 4 &
                      0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) * 39);
              }
              /// @dev Gets the hash of the unit.
              /// @param unitId Unit Id.
              /// @return Unit hash.
              function _getUnitHash(uint256 unitId) internal view virtual returns (bytes32);
              /// @dev Returns unit token URI.
              /// @notice Expected multicodec: dag-pb; hashing function: sha2-256, with base16 encoding and leading CID_PREFIX removed.
              /// @param unitId Unit Id.
              /// @return Unit token URI string.
              function tokenURI(uint256 unitId) public view virtual override returns (string memory) {
                  bytes32 unitHash = _getUnitHash(unitId);
                  // Parse 2 parts of bytes32 into left and right hex16 representation, and concatenate into string
                  // adding the base URI and a cid prefix for the full base16 multibase prefix IPFS hash representation
                  return string(abi.encodePacked(baseURI, CID_PREFIX, _toHex16(bytes16(unitHash)),
                      _toHex16(bytes16(unitHash << 128))));
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.0;
          /// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
          /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
          abstract contract ERC721 {
              /*//////////////////////////////////////////////////////////////
                                           EVENTS
              //////////////////////////////////////////////////////////////*/
              event Transfer(address indexed from, address indexed to, uint256 indexed id);
              event Approval(address indexed owner, address indexed spender, uint256 indexed id);
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /*//////////////////////////////////////////////////////////////
                                   METADATA STORAGE/LOGIC
              //////////////////////////////////////////////////////////////*/
              string public name;
              string public symbol;
              function tokenURI(uint256 id) public view virtual returns (string memory);
              /*//////////////////////////////////////////////////////////////
                                ERC721 BALANCE/OWNER STORAGE
              //////////////////////////////////////////////////////////////*/
              mapping(uint256 => address) internal _ownerOf;
              mapping(address => uint256) internal _balanceOf;
              function ownerOf(uint256 id) public view virtual returns (address owner) {
                  require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
              }
              function balanceOf(address owner) public view virtual returns (uint256) {
                  require(owner != address(0), "ZERO_ADDRESS");
                  return _balanceOf[owner];
              }
              /*//////////////////////////////////////////////////////////////
                                   ERC721 APPROVAL STORAGE
              //////////////////////////////////////////////////////////////*/
              mapping(uint256 => address) public getApproved;
              mapping(address => mapping(address => bool)) public isApprovedForAll;
              /*//////////////////////////////////////////////////////////////
                                         CONSTRUCTOR
              //////////////////////////////////////////////////////////////*/
              constructor(string memory _name, string memory _symbol) {
                  name = _name;
                  symbol = _symbol;
              }
              /*//////////////////////////////////////////////////////////////
                                        ERC721 LOGIC
              //////////////////////////////////////////////////////////////*/
              function approve(address spender, uint256 id) public virtual {
                  address owner = _ownerOf[id];
                  require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
                  getApproved[id] = spender;
                  emit Approval(owner, spender, id);
              }
              function setApprovalForAll(address operator, bool approved) public virtual {
                  isApprovedForAll[msg.sender][operator] = approved;
                  emit ApprovalForAll(msg.sender, operator, approved);
              }
              function transferFrom(
                  address from,
                  address to,
                  uint256 id
              ) public virtual {
                  require(from == _ownerOf[id], "WRONG_FROM");
                  require(to != address(0), "INVALID_RECIPIENT");
                  require(
                      msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
                      "NOT_AUTHORIZED"
                  );
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  unchecked {
                      _balanceOf[from]--;
                      _balanceOf[to]++;
                  }
                  _ownerOf[id] = to;
                  delete getApproved[id];
                  emit Transfer(from, to, id);
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id
              ) public virtual {
                  transferFrom(from, to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id,
                  bytes calldata data
              ) public virtual {
                  transferFrom(from, to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
              /*//////////////////////////////////////////////////////////////
                                        ERC165 LOGIC
              //////////////////////////////////////////////////////////////*/
              function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
                  return
                      interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
                      interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
                      interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
              }
              /*//////////////////////////////////////////////////////////////
                                  INTERNAL MINT/BURN LOGIC
              //////////////////////////////////////////////////////////////*/
              function _mint(address to, uint256 id) internal virtual {
                  require(to != address(0), "INVALID_RECIPIENT");
                  require(_ownerOf[id] == address(0), "ALREADY_MINTED");
                  // Counter overflow is incredibly unrealistic.
                  unchecked {
                      _balanceOf[to]++;
                  }
                  _ownerOf[id] = to;
                  emit Transfer(address(0), to, id);
              }
              function _burn(uint256 id) internal virtual {
                  address owner = _ownerOf[id];
                  require(owner != address(0), "NOT_MINTED");
                  // Ownership check above ensures no underflow.
                  unchecked {
                      _balanceOf[owner]--;
                  }
                  delete _ownerOf[id];
                  delete getApproved[id];
                  emit Transfer(owner, address(0), id);
              }
              /*//////////////////////////////////////////////////////////////
                                  INTERNAL SAFE MINT LOGIC
              //////////////////////////////////////////////////////////////*/
              function _safeMint(address to, uint256 id) internal virtual {
                  _mint(to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
              function _safeMint(
                  address to,
                  uint256 id,
                  bytes memory data
              ) internal virtual {
                  _mint(to, id);
                  if (to.code.length != 0)
                      require(
                          ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                              ERC721TokenReceiver.onERC721Received.selector,
                          "UNSAFE_RECIPIENT"
                      );
              }
          }
          /// @notice A generic interface for a contract which properly accepts ERC721 tokens.
          /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
          abstract contract ERC721TokenReceiver {
              function onERC721Received(
                  address,
                  address,
                  uint256,
                  bytes calldata
              ) external virtual returns (bytes4) {
                  return ERC721TokenReceiver.onERC721Received.selector;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.15;
          /// @dev Errors.
          interface IErrorsRegistries {
              /// @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 Hash already exists in the records.
              error HashExists();
              /// @dev Provided zero address.
              error ZeroAddress();
              /// @dev Agent Id is not correctly provided for the current routine.
              /// @param agentId Component Id.
              error WrongAgentId(uint256 agentId);
              /// @dev Wrong length of two arrays.
              /// @param numValues1 Number of values in a first array.
              /// @param numValues2 Numberf of values in a second array.
              error WrongArrayLength(uint256 numValues1, uint256 numValues2);
              /// @dev Canonical agent Id is not found.
              /// @param agentId Canonical agent Id.
              error AgentNotFound(uint256 agentId);
              /// @dev Component Id is not found.
              /// @param componentId Component Id.
              error ComponentNotFound(uint256 componentId);
              /// @dev Multisig threshold is out of bounds.
              /// @param currentThreshold Current threshold value.
              /// @param minThreshold Minimum possible threshold value.
              /// @param maxThreshold Maximum possible threshold value.
              error WrongThreshold(uint256 currentThreshold, uint256 minThreshold, uint256 maxThreshold);
              /// @dev Agent instance is already registered with a specified `operator`.
              /// @param operator Operator that registered an instance.
              error AgentInstanceRegistered(address operator);
              /// @dev Wrong operator is specified when interacting with a specified `serviceId`.
              /// @param serviceId Service Id.
              error WrongOperator(uint256 serviceId);
              /// @dev Operator has no registered instances in the service.
              /// @param operator Operator address.
              /// @param serviceId Service Id.
              error OperatorHasNoInstances(address operator, uint256 serviceId);
              /// @dev Canonical `agentId` is not found as a part of `serviceId`.
              /// @param agentId Canonical agent Id.
              /// @param serviceId Service Id.
              error AgentNotInService(uint256 agentId, uint256 serviceId);
              /// @dev The contract is paused.
              error Paused();
              /// @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 must be inactive.
              /// @param serviceId Service Id.
              error ServiceMustBeInactive(uint256 serviceId);
              /// @dev All the agent instance slots for a specific `serviceId` are filled.
              /// @param serviceId Service Id.
              error AgentInstancesSlotsFilled(uint256 serviceId);
              /// @dev Wrong state of a service.
              /// @param state Service state.
              /// @param serviceId Service Id.
              error WrongServiceState(uint256 state, uint256 serviceId);
              /// @dev Only own service multisig is allowed.
              /// @param provided Provided address.
              /// @param expected Expected multisig address.
              /// @param serviceId Service Id.
              error OnlyOwnServiceMultisig(address provided, address expected, uint256 serviceId);
              /// @dev Multisig is not whitelisted.
              /// @param multisig Address of a multisig implementation.
              error UnauthorizedMultisig(address multisig);
              /// @dev Incorrect deposit provided for the registration activation.
              /// @param sent Sent amount.
              /// @param expected Expected amount.
              /// @param serviceId Service Id.
              error IncorrectRegistrationDepositValue(uint256 sent, uint256 expected, uint256 serviceId);
              /// @dev Insufficient value provided for the agent instance bonding.
              /// @param sent Sent amount.
              /// @param expected Expected amount.
              /// @param serviceId Service Id.
              error IncorrectAgentBondingValue(uint256 sent, uint256 expected, uint256 serviceId);
              /// @dev Failure of a transfer.
              /// @param token Address of a token.
              /// @param from Address `from`.
              /// @param to Address `to`.
              /// @param value Value.
              error TransferFailed(address token, address from, address to, uint256 value);
              /// @dev Caught reentrancy violation.
              error ReentrancyGuard();
          }