Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/ths-staking/ThsStaking.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {IThsStaking, TierStake} from "./interfaces/IThsStaking.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC1363Receiver} from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol";
import {
ThsStakingStorage,
ThsStakingStorageLib,
Tier,
Epoch,
StakeUpdateList,
StakeUpdate,
Stake,
User,
UserTier
} from "./lib/ThsStakingStorage.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ITiersManager} from "./tiers-managers/interfaces/ITiersManager.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ACLConsumer} from "../aux/ACLConsumer.sol";
contract ThsStaking is
IThsStaking,
UUPSUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable,
IERC1363Receiver,
ACLConsumer
{
using ThsStakingStorageLib for ThsStakingStorage;
using SafeERC20 for IERC20;
uint256 public constant REWARD_SCALE = 1e24;
uint256 public constant BPS_SCALE = 10000;
uint48 public constant EPOCH_DURATION = 1 days;
uint48 public constant STAKE_DURATION_EPOCHS = 30;
IERC20 public immutable thsToken;
IERC20 public immutable wbtc;
address public immutable treasury;
// MODIFIERS
modifier onlyThsSale() {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
require(msg.sender == s.thsSale, OnlyThsSale());
_;
}
// CONSTRUCTOR & INITIALIZER
constructor(address aclManagerAddress, address thsTokenAddress, address wbtcAddress, address treasuryAddress)
ACLConsumer(aclManagerAddress)
{
require(thsTokenAddress != address(0), ZeroAddress());
require(wbtcAddress != address(0), ZeroAddress());
require(treasuryAddress != address(0), ZeroAddress());
_disableInitializers();
thsToken = IERC20(thsTokenAddress);
wbtc = IERC20(wbtcAddress);
treasury = treasuryAddress;
}
function initialize(address tiersManagerAddress) external initializer {
require(tiersManagerAddress != address(0), ZeroAddress());
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__Pausable_init();
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
s.tiersManager = tiersManagerAddress;
}
// MUTATIVE FUNCTIONS
function setThsSale(address newThsSaleAddress) external onlyProtocolAdmin {
require(newThsSaleAddress != address(0), InvalidThsSaleAddress());
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
s.thsSale = newThsSaleAddress;
emit ThsSaleUpdated(newThsSaleAddress);
}
function setTiersManager(address newTiersManagerAddress) external onlyProtocolAdmin {
require(newTiersManagerAddress != address(0), InvalidTiersManagerAddress());
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
s.tiersManager = newTiersManagerAddress;
emit TiersManagerUpdated(newTiersManagerAddress);
}
function pause() external onlyProtocolAdmin {
_pause();
}
function unpause() external onlyProtocolAdmin {
_unpause();
}
function depositDailyRewards(uint256 totalWbtc) external onlyRewardsDistributor nonReentrant {
require(totalWbtc != 0, InvalidWbtcAmount());
wbtc.safeTransferFrom(msg.sender, address(this), totalWbtc);
_distributeRewards(totalWbtc);
}
function stake(uint128 amount) external nonReentrant whenNotPaused {
thsToken.safeTransferFrom(msg.sender, address(this), amount);
_stake(amount, msg.sender, uint48(block.timestamp));
}
function stakeAt(address user, uint128 amount, uint48 startAt) external onlyThsSale whenNotPaused {
require(user != address(0), ZeroAddress());
require(amount > 0, ZeroAmount());
_stake(amount, user, startAt);
}
function unstake(uint8[] calldata tierIds, uint64[][] calldata stakeIds) external nonReentrant whenNotPaused {
_unstake(tierIds, stakeIds);
}
function claimRewards(uint8[] calldata tierIds) external nonReentrant whenNotPaused {
_claimRewards(tierIds);
}
function restake(uint8 tierId, uint64 stakeId) external whenNotPaused {
_restake(tierId, stakeId);
}
// IERC1363Receiver implementation
function onTransferReceived(
address, // operator - not used in this implementation
address from,
uint256 value,
bytes calldata // data - not used in this implementation
)
external
nonReentrant
whenNotPaused
returns (bytes4)
{
require(msg.sender == address(thsToken), InvalidToken());
require(value <= type(uint128).max, InvalidStakeAmount());
_stake(uint128(value), from, uint48(block.timestamp));
return IERC1363Receiver.onTransferReceived.selector;
}
// INTERNAL FUNCTIONS
function _processUserTier(address user, uint8 tierId) internal {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
User storage userStorage = s.users[user];
UserTier storage userTier = userStorage.userTiers[tierId];
Tier storage tier = s.tiers[tierId];
uint48 processUntil = s.lastRewardEpoch;
if (processUntil == 0) {
s.lastRewardEpoch = getCurrentEpoch();
return;
}
uint48 lastProcessed = userTier.lastProcessedEpoch;
if (lastProcessed > processUntil) {
return;
}
uint128 balance = userTier.totalStakedThs;
uint128 accruedRewards = 0;
uint48 incomingEpoch = userTier.incomingThs.startEpoch;
uint48 outgoingEpoch = userTier.outgoingThs.startEpoch;
uint48 lastUserProcessedEpoch = lastProcessed;
while (lastUserProcessedEpoch < processUntil) {
uint48 nextEventEpoch = processUntil;
if (incomingEpoch != 0 && incomingEpoch < nextEventEpoch) {
nextEventEpoch = incomingEpoch;
}
if (outgoingEpoch != 0 && outgoingEpoch < nextEventEpoch) {
nextEventEpoch = outgoingEpoch;
}
uint128 oldBalance = balance;
uint256 prevScaledReward = tier.epochs[lastUserProcessedEpoch].scaledRewardPerThs;
// Check if we're processing multiple epochs at once (gap between processed and event epochs)
if (nextEventEpoch > lastUserProcessedEpoch + 1) {
// Calculate rewards for the entire skipped period using the reward rate at the end of the period
uint256 endPeriodReward = tier.epochs[nextEventEpoch - 1].scaledRewardPerThs;
if (endPeriodReward > prevScaledReward && oldBalance > 0) {
// Accrue rewards for the period from lastUserProcessedEpoch to nextEventEpoch-1
accruedRewards += SafeCast.toUint128(
Math.mulDiv(uint256(oldBalance), endPeriodReward - prevScaledReward, REWARD_SCALE)
);
}
// Update prevScaledReward for the final epoch segment calculation
prevScaledReward = endPeriodReward;
}
if (nextEventEpoch == incomingEpoch) {
balance += userTier.incomingThs.updates[nextEventEpoch].thsAmount;
incomingEpoch = userTier.incomingThs.updates[nextEventEpoch].nextUpdateEpoch;
delete userTier.incomingThs.updates[nextEventEpoch];
if (incomingEpoch == 0) {
userTier.incomingThs.endEpoch = 0;
}
}
if (nextEventEpoch == outgoingEpoch) {
balance -= userTier.outgoingThs.updates[nextEventEpoch].thsAmount;
outgoingEpoch = userTier.outgoingThs.updates[nextEventEpoch].nextUpdateEpoch;
delete userTier.outgoingThs.updates[nextEventEpoch];
if (outgoingEpoch == 0) {
userTier.outgoingThs.endEpoch = 0;
}
}
uint256 currentScaledReward = tier.epochs[nextEventEpoch].scaledRewardPerThs;
// Accrue rewards for the final epoch segment (from nextEventEpoch-1 to nextEventEpoch)
if (currentScaledReward > prevScaledReward && balance > 0) {
accruedRewards += SafeCast.toUint128(
Math.mulDiv(uint256(balance), currentScaledReward - prevScaledReward, REWARD_SCALE)
);
}
lastUserProcessedEpoch = nextEventEpoch;
}
userTier.totalStakedThs = balance;
userTier.lastProcessedEpoch = processUntil;
if (accruedRewards > 0) {
userStorage.unclaimedRewards += accruedRewards;
}
userTier.incomingThs.startEpoch = incomingEpoch;
if (incomingEpoch != 0) {
userTier.incomingThs.updates[incomingEpoch].prevUpdateEpoch = 0;
}
userTier.outgoingThs.startEpoch = outgoingEpoch;
if (outgoingEpoch != 0) {
userTier.outgoingThs.updates[outgoingEpoch].prevUpdateEpoch = 0;
}
}
function _stake(uint128 amount, address user, uint48 start) internal {
require(amount > 0, InvalidStakeAmount());
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
uint8 tierId = ITiersManager(s.tiersManager).getTierIdByAmount(amount);
_processUserTier(user, tierId);
uint48 entryEpoch = _timestampToEpoch(start) + 1;
uint48 expiryEpoch = entryEpoch + STAKE_DURATION_EPOCHS;
UserTier storage userTier = s.users[user].userTiers[tierId];
s.tiers[tierId].epochs[entryEpoch].incomingThs += amount;
s.tiers[tierId].epochs[expiryEpoch].outgoingThs += amount;
_insertStakeUpdate(userTier.incomingThs, entryEpoch, amount);
_insertStakeUpdate(userTier.outgoingThs, expiryEpoch, amount);
uint64 stakeId = ++s.lastStakeId;
userTier.stakes[stakeId] = Stake({start: start, expiry: expiryEpoch * EPOCH_DURATION, amount: uint128(amount)});
emit Staked(user, amount, tierId, stakeId, entryEpoch, expiryEpoch);
}
function _unstake(uint8[] calldata tierIds, uint64[][] calldata stakeIds) internal {
require(tierIds.length == stakeIds.length, InvalidInputLength());
for (uint8 i = 0; i < tierIds.length; i++) {
_processUnstakeInTier(msg.sender, tierIds[i], stakeIds[i]);
}
}
function _processUnstakeInTier(address user, uint8 tierId, uint64[] calldata stakeIds) internal {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
UserTier storage userTier = s.users[user].userTiers[tierId];
uint128 totalWithdrawn = 0;
uint48 currentEpoch = getCurrentEpoch();
for (uint256 i = 0; i < stakeIds.length; i++) {
uint64 stakeId = stakeIds[i];
Stake memory stakeData = userTier.stakes[stakeId];
uint128 amount = stakeData.amount;
require(amount != 0, StakeNotFound());
require(block.timestamp >= stakeData.start, StakeNotStarted());
uint48 entryEpoch = _timestampToEpoch(stakeData.start) + 1;
uint48 expiryEpoch = _timestampToEpoch(stakeData.expiry);
if (block.timestamp < stakeData.expiry) {
if (entryEpoch > currentEpoch) {
// Stake hasn't started yet: just cancel future incoming
s.tiers[tierId].epochs[entryEpoch].incomingThs -= amount;
_removeStakeUpdate(userTier.incomingThs, entryEpoch, amount);
_processUserTier(user, tierId);
} else {
// Stake is active: move the outgoing to current epoch to exclude from next distribution
s.tiers[tierId].epochs[currentEpoch].outgoingThs += amount;
_insertStakeUpdate(userTier.outgoingThs, currentEpoch, amount);
}
// Always remove the future expiry
s.tiers[tierId].epochs[expiryEpoch].outgoingThs -= amount;
_removeStakeUpdate(userTier.outgoingThs, expiryEpoch, amount);
}
totalWithdrawn += amount;
delete userTier.stakes[stakeId];
emit Unstaked(user, amount, tierId, stakeId);
}
_processUserTier(user, tierId);
thsToken.safeTransfer(user, totalWithdrawn);
}
function _claimRewards(uint8[] calldata tierIds) internal {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
User storage userStorage = s.users[msg.sender];
for (uint256 i = 0; i < tierIds.length; i++) {
_processUserTier(msg.sender, tierIds[i]);
}
uint256 rewardsToClaim = userStorage.unclaimedRewards;
require(rewardsToClaim > 0, NoRewardsToClaim());
userStorage.unclaimedRewards = 0;
wbtc.safeTransfer(msg.sender, rewardsToClaim);
emit RewardsClaimed(msg.sender, rewardsToClaim);
}
function _restake(uint8 tierId, uint64 stakeId) internal {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
UserTier storage userTier = s.users[msg.sender].userTiers[tierId];
Stake memory stakeData = userTier.stakes[stakeId];
uint128 amount = stakeData.amount;
require(amount != 0, StakeNotFound());
uint48 expiryEpoch = _timestampToEpoch(stakeData.expiry);
uint48 currentEpoch = getCurrentEpoch();
require(currentEpoch >= expiryEpoch - 1, RestakeTooEarly());
_processUserTier(msg.sender, tierId);
uint64 oldStakeId = stakeId;
uint8 oldTierId = tierId;
if (currentEpoch < expiryEpoch) {
// Prolongation scenario: restaking before expiry epoch
// Remove the original outgoing update from expiryEpoch
s.tiers[tierId].epochs[expiryEpoch].outgoingThs -= amount;
_removeStakeUpdate(userTier.outgoingThs, expiryEpoch, amount);
// Add new outgoing update at prolonged expiry
uint48 newExpiryEpoch = expiryEpoch + STAKE_DURATION_EPOCHS;
s.tiers[tierId].epochs[newExpiryEpoch].outgoingThs += amount;
_insertStakeUpdate(userTier.outgoingThs, newExpiryEpoch, amount);
// Update stake storage with new expiry
userTier.stakes[stakeId].expiry = newExpiryEpoch * EPOCH_DURATION;
emit Restaked(msg.sender, amount, oldTierId, oldStakeId, tierId, stakeId, expiryEpoch, newExpiryEpoch);
} else {
// New stake scenario: restaking at or after expiry epoch
delete userTier.stakes[stakeId];
uint48 newStart = currentEpoch * EPOCH_DURATION;
uint8 newTierId = ITiersManager(s.tiersManager).getTierIdByAmount(amount);
_stake(amount, msg.sender, newStart);
uint64 newStakeId = s.lastStakeId;
uint48 newEntryEpoch = _timestampToEpoch(newStart) + 1;
uint48 newExpiryEpoch = newEntryEpoch + STAKE_DURATION_EPOCHS;
emit Restaked(
msg.sender, amount, oldTierId, oldStakeId, newTierId, newStakeId, newEntryEpoch, newExpiryEpoch
);
}
}
function _insertStakeUpdate(StakeUpdateList storage list, uint48 epoch, uint128 amount) internal {
StakeUpdate storage update = list.updates[epoch];
if (update.thsAmount > 0) {
update.thsAmount += amount;
return;
}
uint48 startEpoch = list.startEpoch;
uint48 endEpoch = list.endEpoch;
if (startEpoch == 0) {
list.startEpoch = list.endEpoch = epoch;
list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: 0, nextUpdateEpoch: 0});
} else if (epoch < startEpoch) {
list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: 0, nextUpdateEpoch: startEpoch});
list.updates[startEpoch].prevUpdateEpoch = epoch;
list.startEpoch = epoch;
} else if (epoch > endEpoch) {
list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: endEpoch, nextUpdateEpoch: 0});
list.updates[endEpoch].nextUpdateEpoch = epoch;
list.endEpoch = epoch;
} else {
uint48 current = startEpoch;
uint48 next = list.updates[current].nextUpdateEpoch;
while (next < epoch) {
current = next;
next = list.updates[current].nextUpdateEpoch;
}
list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: current, nextUpdateEpoch: next});
list.updates[current].nextUpdateEpoch = epoch;
list.updates[next].prevUpdateEpoch = epoch;
}
}
function _removeStakeUpdate(StakeUpdateList storage list, uint48 epoch, uint128 amount) internal {
StakeUpdate storage update = list.updates[epoch];
require(update.thsAmount >= amount, InvalidStakeAmount());
update.thsAmount -= amount;
if (update.thsAmount == 0) {
uint48 prev = update.prevUpdateEpoch;
uint48 next = update.nextUpdateEpoch;
if (prev != 0) {
list.updates[prev].nextUpdateEpoch = next;
} else {
list.startEpoch = next;
}
if (next != 0) {
list.updates[next].prevUpdateEpoch = prev;
} else {
list.endEpoch = prev;
}
delete list.updates[epoch];
}
}
function _distributeRewards(uint256 totalWbtc) internal {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
(uint8[] memory tierIds, uint256[] memory feeBps) = ITiersManager(s.tiersManager).getTiers();
uint256 tierCount = tierIds.length;
uint48 currentEpoch = getCurrentEpoch();
uint48 lastRewardEpoch = s.lastRewardEpoch;
uint128[] memory activeStakedInTiers = new uint128[](tierCount);
uint256 totalStakedAcrossAllTiers;
// Enforce processing strictly previous epochs while supporting backfilling
uint48 epochToProcess;
if (lastRewardEpoch == 0) {
require(currentEpoch > 0, InvalidEpoch());
epochToProcess = currentEpoch - 1;
} else {
require(lastRewardEpoch < currentEpoch - 1, InvalidEpoch());
epochToProcess = lastRewardEpoch + 1;
}
for (uint256 i = 0; i < tierCount; i++) {
uint8 tierId = tierIds[i];
Tier storage tier = s.tiers[tierId];
uint128 totalStakedThs = tier.totalStakedThs;
// Apply flows for the epoch being processed
totalStakedThs =
totalStakedThs + tier.epochs[epochToProcess].incomingThs - tier.epochs[epochToProcess].outgoingThs;
activeStakedInTiers[i] = totalStakedThs;
totalStakedAcrossAllTiers += totalStakedThs;
tier.totalStakedThs = totalStakedThs;
}
uint256 totalNetRewardsForAllStakers;
// If no active stakers, send 100% to treasury
if (totalStakedAcrossAllTiers == 0) {
wbtc.safeTransfer(treasury, totalWbtc);
emit RewardsDeposited(totalWbtc, totalWbtc, 0, uint8(tierCount));
s.lastRewardEpoch = epochToProcess;
return;
}
for (uint256 i = 0; i < tierCount; i++) {
uint8 tierId = tierIds[i];
Tier storage tier = s.tiers[tierId];
uint128 stakedInTier = activeStakedInTiers[i];
uint256 prevScaledRewardPerThs = tier.epochs[lastRewardEpoch].scaledRewardPerThs;
if (stakedInTier == 0) {
tier.epochs[epochToProcess].scaledRewardPerThs = prevScaledRewardPerThs;
emit NoStakedThsInTier(epochToProcess, tierId, prevScaledRewardPerThs);
continue;
}
uint256 grossRewardsForTier = Math.mulDiv(totalWbtc, stakedInTier, totalStakedAcrossAllTiers);
uint256 netRewardsForTierStakers = Math.mulDiv(grossRewardsForTier, BPS_SCALE - feeBps[i], BPS_SCALE);
totalNetRewardsForAllStakers += netRewardsForTierStakers;
uint256 tierDeltaPerThs = Math.mulDiv(netRewardsForTierStakers, REWARD_SCALE, uint256(stakedInTier));
uint256 newScaledTotal = prevScaledRewardPerThs + tierDeltaPerThs;
Epoch storage epochData = tier.epochs[epochToProcess];
epochData.scaledRewardPerThs = newScaledTotal;
emit RewardsDepositedPerTier(epochToProcess, tierId, stakedInTier, grossRewardsForTier, tierDeltaPerThs);
}
s.lastRewardEpoch = epochToProcess;
uint256 treasuryFee = totalWbtc - totalNetRewardsForAllStakers;
if (treasuryFee > 0) {
wbtc.safeTransfer(treasury, treasuryFee);
}
emit RewardsDeposited(totalWbtc, treasuryFee, totalStakedAcrossAllTiers, uint8(activeStakedInTiers.length));
}
function getCurrentEpoch() public view returns (uint48) {
return _timestampToEpoch(uint48(block.timestamp));
}
function getTiersManager() external view returns (address) {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
return s.tiersManager;
}
function getLastRewardEpoch() external view returns (uint48) {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
return s.lastRewardEpoch;
}
function getThsSale() external view returns (address) {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
return s.thsSale;
}
function getTotalStakedAcrossAllTiers() external view returns (TierStake[] memory tierStakes, uint256 totalStaked) {
ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
(uint8[] memory tierIds,) = ITiersManager(s.tiersManager).getTiers();
uint256 tierCount = tierIds.length;
uint48 currentEpoch = getCurrentEpoch();
uint48 lastRewardEpoch = s.lastRewardEpoch;
tierStakes = new TierStake[](tierCount);
totalStaked = 0;
uint48 epochToProcess;
if (lastRewardEpoch == 0) {
require(currentEpoch > 0, InvalidEpoch());
epochToProcess = currentEpoch - 1;
} else {
require(lastRewardEpoch < currentEpoch - 1, InvalidEpoch());
epochToProcess = lastRewardEpoch + 1;
}
for (uint256 i = 0; i < tierCount; i++) {
uint8 tierId = tierIds[i];
Tier storage tier = s.tiers[tierId];
uint128 totalStakedThs = tier.totalStakedThs;
totalStakedThs =
totalStakedThs + tier.epochs[epochToProcess].incomingThs - tier.epochs[epochToProcess].outgoingThs;
tierStakes[i] = TierStake({tierId: tierId, totalStaked: totalStakedThs});
totalStaked += totalStakedThs;
}
return (tierStakes, totalStaked);
}
function _timestampToEpoch(uint48 timestamp) internal pure returns (uint48) {
return timestamp / EPOCH_DURATION;
}
// UUPS UPGRADE
function _authorizeUpgrade(address newImplementation) internal override onlyProtocolAdmin {}
}
"
},
"src/ths-staking/interfaces/IThsStaking.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {IThsStakingEvents} from "./IThsStakingEvents.sol";
import {IThsStakingErrors} from "./IThsStakingErrors.sol";
struct TierStake {
uint8 tierId;
uint128 totalStaked;
}
interface IThsStaking is IThsStakingEvents, IThsStakingErrors {
function setTiersManager(address newTiersManagerAddress) external;
function depositDailyRewards(uint256 totalWbtc) external;
function stake(uint128 amount) external;
function stakeAt(address user, uint128 amount, uint48 startAt) external;
function unstake(uint8[] calldata tierIds, uint64[][] calldata stakeIds) external;
function claimRewards(uint8[] calldata tierIds) external;
function restake(uint8 tierId, uint64 stakeId) external;
function getTiersManager() external view returns (address);
function getLastRewardEpoch() external view returns (uint48);
function getThsSale() external view returns (address);
function setThsSale(address newThsSaleAddress) external;
function getTotalStakedAcrossAllTiers() external view returns (TierStake[] memory tierStakes, uint256 totalStaked);
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
OwnableStorage storage $ = _getOwnableStorage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
function __Pausable_init() internal onlyInitializing {
}
function __Pausable_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363Receiver.sol)
pragma solidity >=0.5.0;
/**
* @title IERC1363Receiver
* @dev Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall`
* from ERC-1363 token contracts.
*/
interface IERC1363Receiver {
/**
* @dev Whenever ERC-1363 tokens are transferred to this contract via `transferAndCall` or `transferFromAndCall`
* by `operator` from `from`, this function is called.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))`
* (i.e. 0x88a7ca5c, or its own function selector).
*
* @param operator The address which called `transferAndCall` or `transferFromAndCall` function.
* @param from The address which the tokens are transferred from.
* @param value The amount of tokens transferred.
* @param data Additional data with no specified format.
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` if transfer is allowed unless throwing.
*/
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes calldata data
) external returns (bytes4);
}
"
},
"src/ths-staking/lib/ThsStakingStorage.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
struct Epoch {
uint128 incomingThs;
uint128 outgoingThs;
uint256 scaledRewardPerThs;
}
struct Tier {
uint128 totalStakedThs;
mapping(uint48 => Epoch) epochs;
}
struct StakeUpdate {
uint128 thsAmount;
uint48 prevUpdateEpoch;
uint48 nextUpdateEpoch;
}
struct StakeUpdateList {
uint48 startEpoch;
uint48 endEpoch;
mapping(uint48 => StakeUpdate) updates;
}
struct UserTier {
uint128 totalStakedThs;
uint48 lastProcessedEpoch;
StakeUpdateList incomingThs;
StakeUpdateList outgoingThs;
mapping(uint64 => Stake) stakes;
}
struct User {
uint128 unclaimedRewards;
mapping(uint8 => UserTier) userTiers;
}
struct Stake {
// Actual start, not epoch
uint48 start;
// Actual expiry, not epoch
uint48 expiry;
uint128 amount;
}
struct ThsStakingStorage {
mapping(uint8 => Tier) tiers;
mapping(address => User) users;
uint64 lastStakeId;
address tiersManager;
uint48 lastRewardEpoch;
address thsSale;
}
library ThsStakingStorageLib {
bytes32 private constant THS_STAKING_STORAGE_POSITION = bytes32(uint256(keccak256("ths-staking.storage")) - 1);
function getStorage() internal pure returns (ThsStakingStorage storage s) {
bytes32 position = THS_STAKING_STORAGE_POSITION;
assembly {
s.slot := position
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit addition of two uint256.
*
* The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
*/
function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
assembly ("memory-safe") {
low := add(a, b)
high := lt(low, a)
}
}
/**
* @dev Return the 512-bit multiplication of two uint256.
*
* The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
*/
function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
// 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = high * 2²⁵⁶ + low.
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
low := mul(a, b)
high := sub(sub(mm, low), lt(mm, low))
}
}
/**
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
success = c >= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a - b;
success = c <= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a * b;
assembly ("memory-safe") {
// Only true when the multiplication doesn't overflow
// (c / a == b) || (a == 0)
success := or(eq(div(c, a), b), iszero(a))
}
// equivalent to: success ? c : 0
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `DIV` opcode returns zero when the denominator is 0.
result := div(a, b)
}
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `MOD` opcode returns zero when the denominator is 0.
result := mod(a, b)
}
}
}
/**
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryAdd(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
*/
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
(, uint256 result) = trySub(a, b);
return result;
}
/**
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryMul(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
// Handle non-overflow cases, 256 by 256 division.
if (high == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return low / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= high) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [high low].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
high := sub(high, gt(remainder, low))
low := sub(low, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly ("memory-safe") {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [high low] by twos.
low := div(low, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from high into low.
low |= high * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
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⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// 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²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
// is no longer required.
result = low * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
*/
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
if (high >= 1 << n) {
Panic.panic(Panic.UNDER_OVERFLOW);
}
return (high << (256 - n)) | (low >> n);
}
}
/**
* @dev Calculates x * y >> n with full precision, following the selected rounding direction.
*/
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a
Submitted on: 2025-11-05 14:59:09
Comments
Log in to comment.
No comments yet.