Transaction Hash:
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 | ||
---|---|---|---|---|---|
0x0001A500...c28E45CB0 | |||||
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 7.514735862013521123 Eth | 7.515092299513521123 Eth | 0.0003564375 | |
0xa0DA5344...628B30f82 | 39.816617807575900339 Eth | 39.646617807575900339 Eth | 0.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 )
- ETH 0.17
-
OLAS.balanceOf( 0xEe9b087C43c1A267cFDC63f14ceC451c8b305997 ) => ( 6255915977741059281606 )
claimOwnerIncentives[Dispenser (ln:724)]
ReentrancyGuard[Dispenser (ln:730)]
paused[Dispenser (ln:736)]
Paused[Dispenser (ln:737)]
accountOwnerIncentives[Dispenser (ln:740)]
balanceOf[Dispenser (ln:747)]
withdrawToAccount[Dispenser (ln:749)]
balanceOf[Dispenser (ln:752)]
WrongAmount[Dispenser (ln:754)]
ClaimIncentivesFailed[Dispenser (ln:760)]
IncentivesClaimed[Dispenser (ln:762)]
File 1 of 7: Dispenser
File 2 of 7: Treasury
File 3 of 7: OLAS
File 4 of 7: TokenomicsProxy
File 5 of 7: Tokenomics
File 6 of 7: ComponentRegistry
File 7 of 7: AgentRegistry
// 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(); }