Contract Source Code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import {IPyth} from "pyth-sdk-solidity/IPyth.sol";
import {PythStructs} from "pyth-sdk-solidity/PythStructs.sol";
import {OwnableRoles} from "solady/auth/OwnableRoles.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {IRebootTournament} from "./interfaces/IRebootTournament.sol";
import {Direction, Trade, InternalPrice, TournamentConfig} from "./structs/TopTraderStructs.sol";
/**
* * * * * * * * * * * * * * * * * * * * * * * * *
* _______ _____ _____ *
* | | | |_____] *
* | |_____| | *
* *
* _______ ______ _______ ______ _______ ______ *
* | |_____/ |_____| | \ |______ |_____/ *
* | | \_ | | |_____/ |______ | \_ *
* * * * * * * * * * * * * * * * * * * * * * * * * *
*/
contract TopTrader is OwnableRoles {
// The price is older than the valid time period.
error StalePrice();
// block.timestamp is not within the tournament's start and end time.
error NotActive();
// The pair does not match the current active trade.
error MismatchedPairs();
// A transfer has reverted.
error TransferFailed();
// The amount is 0.
error InvalidAmount();
// The user has already entered the tournament.
error AlreadyEntered();
// Must provide entry fee in the form of msg.value.
error InsufficientEntryFee();
// Prices cannot be finalized while the tournament is active.
error TournamentActive();
// The user does not have enough balance to execute the trade.
error InsufficientBalance(int256 balanceInBips, int256 requiredAmountInBips);
// The minimum hold duration has not been met.
error MinimumHoldDurationNotMet();
// The slippage scaling factor is too high.
error SlippageTooHigh();
// The liquidation bounty is too high.
error LiquidationBountyTooHigh();
// An address is initialized to the zero address.
error ZeroAddress();
// The minimum timestamp has not been met.
error MinTimestampNotMet();
// The signature is invalid.
error InvalidSignature();
// The nonce has already been used.
error NonceAlreadyUsed();
// The caller is not the counterparty signer or the trader.
error InvalidCaller();
// The signature has expired.
error SignatureExpired();
// Trade function allows price updates of length 1 only.
error InvalidPriceUpdates();
// The length of the pairs and prices arrays do not match.
error ArrayLengthMismatch();
// The price update publish time is in the future.
error InvalidTimestamp();
// Emitted when a new tournament is created.
event TournamentCreated(
uint256 indexed tournamentId,
bytes32[] pairs,
uint40 startTime,
uint40 endTime,
uint128 startBalance,
uint16 maxLeverage,
uint80 entryFee,
uint16 minimumHoldDuration,
uint64 rebuyLimit,
uint256 bonusCollateral
);
// Emitted when a user is successfully liquidated.
event Liquidated(uint256 indexed tournamentId, address indexed user);
// The user does not have a negative (balance + upnl).
event InvalidLiquidation(uint256 indexed tournamentId, address indexed user, int256 balance, int256 pnl);
// Emitted when a liquidation bounty is paid.
event LiquidationBountyPaid(address indexed liquidator, uint256 amount);
// Emitted when a trade is executed, to provide trade details.
event TradeExecuted(
uint256 indexed tournamentId,
uint256 indexed nonce,
address indexed user,
int256 amount,
int256 executionPrice,
bytes32 pair
);
// Emitted when a trade is executed, to provide new account details.
event AccountUpdated(
uint256 indexed tournamentId,
address indexed user,
int256 indexed balance,
int256 amount,
int256 entryPrice,
bytes32 pair
);
// Emitted when the base slippage scaling factor is updated.
event BaseSlippageScalingFactorUpdated(uint256 newFactor);
// Emitted when the slippage scaling factor is updated.
event SlippageScalingFactorUpdated(uint256 newFactor);
// Emitted when the liquidation bounty is updated.
event LiquidationBountyUpdated(uint256 newBounty);
// Emitted when the counterparty signer is updated.
event CounterpartySignerUpdated(address newSigner);
IPyth private immutable _PYTH;
IRebootTournament private immutable _REBOOT;
address private immutable _WAPE_CONTRACT_ADDRESS;
uint64 private constant _FINAL_ENDTIME_GRACE_PERIOD = 5 seconds;
int256 private constant _BIPS = 10_000;
int256 private constant _FIRST_SLIPPAGE_BREAKPOINT = 1_000_000;
int256 private constant _SECOND_SLIPPAGE_BREAKPOINT = 10_000_000;
uint256 private constant _GLOBAL_CONFIG_MANAGER = _ROLE_42;
uint256 private constant _TOURNAMENT_MANAGER = _ROLE_69;
uint256 private constant _PRICE_MANAGER = _ROLE_88;
uint256 private constant _MAX_BASE_SLIPPAGE_SCALING_FACTOR = 250;
uint256 private constant _MAX_SLIPPAGE_INCREMENT_SCALING_FACTOR = 10;
uint256 public constant SIGNATURE_VALIDITY_DURATION = 30 seconds;
bytes4 private constant _MANUAL_PRICE_PAIR_IDENTIFIER = 0x00420069;
address public counterpartySigner;
uint256 public liquidationBounty;
int256 public baseSlippageScalingFactor;
int256 public slippageIncrementScalingFactor;
mapping(uint256 tournamentId => TournamentConfig tournament) public tournaments;
mapping(bytes32 pair => InternalPrice price) public internalPrices;
mapping(uint256 nonce => bool isUsed) public usedNonces;
constructor(
address pyth,
address reboot,
address wape,
address signer,
uint256 _liquidationBounty,
uint256 _baseSlippage,
uint256 _slippageIncrement
) {
if (pyth == address(0) || reboot == address(0) || wape == address(0)) revert ZeroAddress();
_initializeOwner(tx.origin);
_grantRoles(tx.origin, _GLOBAL_CONFIG_MANAGER | _TOURNAMENT_MANAGER);
_PYTH = IPyth(pyth);
_REBOOT = IRebootTournament(reboot);
_WAPE_CONTRACT_ADDRESS = wape;
liquidationBounty = _liquidationBounty;
_setCounterpartySigner(signer);
_setBaseSlippageScalingFactor(_baseSlippage);
_setSlippageIncrementScalingFactor(_slippageIncrement);
}
receive() external payable {}
/**
* @notice Creates a new tournament with the given parameters.
* @param pairs The pairs that are allowed in the tournament (signature enforced).
* @param startTime The start time of the tournament.
* @param endTime The end time of the tournament.
* @param startBalance The starting balance for each user.
* @param maxLeverage The maximum leverage allowed for each user.
* @param entryFee The entry fee in $APE for the tournament.
* @param rebuyLimit The maximum number of rebuys allowed per user, after initial entry.
* @param bonusCollateral Optional bonus collateral pool.
*/
function createTournament(
bytes32[] calldata pairs,
uint16 minimumHoldDuration,
uint40 startTime,
uint40 endTime,
uint128 startBalance,
uint16 maxLeverage,
uint80 entryFee,
uint64 rebuyLimit,
uint256 bonusCollateral
) external onlyRoles(_TOURNAMENT_MANAGER) returns (uint256 tournamentId) {
tournamentId = _REBOOT.register(
IRebootTournament.Config({
entryFee: entryFee,
rebuyFee: entryFee,
isExactFee: true,
entryLimit: type(uint64).max,
maxEntriesPerRoom: type(uint64).max,
rebuyLimit: rebuyLimit,
startDate: startTime,
endDate: endTime,
ticketProfitToTickets: 1 ether,
creditProfitToTickets: 1 ether,
creditEntryToTickets: 1 ether,
entryDuration: endTime - startTime,
tournamentDuration: 0,
payoutDuration: 30 days,
creditRatio: 0,
creditId: _WAPE_CONTRACT_ADDRESS,
bonusCollateral: bonusCollateral,
priceFeedPair: 0x0
})
);
_REBOOT.createRoom(tournamentId, 0, uint64(startTime));
TournamentConfig storage tournament = tournaments[tournamentId];
tournament.minimumHoldDuration = minimumHoldDuration;
tournament.startTime = startTime;
tournament.endTime = endTime;
tournament.startBalance = startBalance;
tournament.maxLeverage = maxLeverage;
emit TournamentCreated(
tournamentId,
pairs,
startTime,
endTime,
startBalance,
maxLeverage,
entryFee,
minimumHoldDuration,
rebuyLimit,
bonusCollateral
);
return tournamentId;
}
/**
* @notice Enters a user into a tournament.
* @param tournamentId The ID of the tournament.
*/
function enterTournament(uint256 tournamentId) external payable {
_enterTournament(msg.sender, tournamentId);
}
/**
* @notice Enters a user into a tournament
* @param user The address of the user
* @param tournamentId The ID of the tournament
*/
function enterTournamentFor(address user, uint256 tournamentId) external payable {
_enterTournament(user, tournamentId);
}
/**
* @notice Executes a trade for a user in a tournament.
* @param tournamentId The ID of the tournament.
* @param pair The pair to trade. If the pair is not supported by Pyth, it should begin with _MANUAL_PRICE_PAIR_IDENTIFIER.
* @param priceUpdates The Pyth price update data (empty if pair is not supported by Pyth).
* @param amountInBips The amount to trade in bips. Positive for long, negative for short.
* @param trader The address of the trader. If not msg.sender, `signature` must recover to msg.sender.
* @param minTimestamp The minimum timestamp for the trade.
* @param nonce The random nonce for the trade.
* @param signature The signature of the trader, if trader != msg.sender.
*/
function trade(
uint256 tournamentId,
bytes32 pair,
bytes[] calldata priceUpdates,
int256 amountInBips,
address trader,
uint256 minTimestamp,
uint256 nonce,
bytes calldata signature
) external {
uint256 signatureExpirationTimestamp;
unchecked {
signatureExpirationTimestamp = minTimestamp + SIGNATURE_VALIDITY_DURATION;
}
if (amountInBips == 0) revert InvalidAmount();
if (block.timestamp < minTimestamp) revert MinTimestampNotMet();
if (block.timestamp > signatureExpirationTimestamp) revert SignatureExpired();
TournamentConfig storage tournament = tournaments[tournamentId];
_assertTournamentOpen(tournament);
Trade memory currentPosition = tournament.activeTrade[trader];
bool isReduce = false;
if (currentPosition.pair != 0x0) {
if (currentPosition.pair != pair) {
revert MismatchedPairs();
} else if (
// Enforce minimum hold duration if the user has an open position and is reducing or closing it.
(currentPosition.amountInBips > 0) != (amountInBips > 0)
) {
if (currentPosition.lastUpdatedAt + tournament.minimumHoldDuration > block.timestamp) {
revert MinimumHoldDurationNotMet();
}
isReduce = true;
}
}
// Prevent signature re-use.
if (usedNonces[nonce]) revert NonceAlreadyUsed();
usedNonces[nonce] = true;
Direction direction = amountInBips > 0 ? Direction.LONG : Direction.SHORT;
bytes32 hash = SignatureCheckerLib.toEthSignedMessageHash(
keccak256(abi.encodePacked(trader, tournamentId, pair, direction, minTimestamp, nonce))
);
if (msg.sender == trader) {
// If trader is submitting the trade, ensure counterparty has signed for it.
if (!SignatureCheckerLib.isValidSignatureNowCalldata(counterpartySigner, hash, signature)) {
revert InvalidSignature();
}
} else if (msg.sender == counterpartySigner) {
// If counterparty is submitting trade, ensure the trader has signed for it.
if (!SignatureCheckerLib.isValidSignatureNowCalldata(trader, hash, signature)) {
revert InvalidSignature();
}
} else {
// In real life this is where a bouncer would break your kneecaps.
revert InvalidCaller();
}
updatePythPriceFeeds(priceUpdates);
int256 normalizedPrice = _fetchAndNormalizePrice(pair);
int256 currentUSDNPnlInBips = _currentPnlForPositionAndPrice(currentPosition, normalizedPrice);
int256 usdnBalance = tournament.userBalance[trader];
int256 absoluteActivePositionAmountInBips = _abs(currentPosition.amountInBips);
int256 usdnMaxTradeInBips;
unchecked {
// At very large balances, this math will over/underflow and cause unintended side effects.
// Break the contract. Achieve eternal glory. Bet more.
int256 usdnTotalAvailableCapital =
(usdnBalance * _BIPS + currentUSDNPnlInBips) * int256(uint256(tournament.maxLeverage)) / _BIPS;
int256 absoluteExistingExposure = absoluteActivePositionAmountInBips * normalizedPrice / _BIPS;
usdnMaxTradeInBips = (
isReduce
? usdnTotalAvailableCapital + absoluteExistingExposure
: usdnTotalAvailableCapital - absoluteExistingExposure
) * _BIPS;
}
int256 absoluteAmountInBips = _abs(amountInBips);
// Leave this checked to prevent malicious over/underflow in subsequent calcs.
int256 tradeSizeInBips = absoluteAmountInBips * normalizedPrice;
if (tradeSizeInBips > usdnMaxTradeInBips) {
// Set max trade to 0 if negative, for event clarity.
if (usdnMaxTradeInBips < 0) usdnMaxTradeInBips = 0;
revert InsufficientBalance(usdnMaxTradeInBips, tradeSizeInBips);
}
int104 openAmount;
if (isReduce) {
if (absoluteAmountInBips == absoluteActivePositionAmountInBips) {
// Position is being closed. Should never be used. Liquidation or glory.
unchecked {
usdnBalance += (currentUSDNPnlInBips / _BIPS);
}
// Apply profits and close position.
tournament.userBalance[trader] = usdnBalance;
tournament.activeTrade[trader] = Trade(0x0, uint40(block.timestamp), 0, 0);
emit TradeExecuted(tournamentId, nonce, trader, amountInBips, normalizedPrice, pair);
emit AccountUpdated(tournamentId, trader, tournament.userBalance[trader], 0, 0, 0x0);
} else if (absoluteAmountInBips < absoluteActivePositionAmountInBips) {
// Position is being reduced. For use by cowards only.
unchecked {
openAmount = currentPosition.amountInBips + int104(amountInBips);
usdnBalance +=
currentUSDNPnlInBips * absoluteAmountInBips / absoluteActivePositionAmountInBips / _BIPS;
}
// Apply scaled profits, reduce position, and reset open timstamp.
tournament.userBalance[trader] = usdnBalance;
tournament.activeTrade[trader].amountInBips = openAmount;
tournament.activeTrade[trader].lastUpdatedAt = uint40(block.timestamp);
emit TradeExecuted(tournamentId, nonce, trader, amountInBips, normalizedPrice, pair);
emit AccountUpdated(
tournamentId, trader, tournament.userBalance[trader], openAmount, currentPosition.entryPrice, pair
);
} else {
// Position is being flipped. For use by the candle chasers.
// We need to close and apply profits without slippage, then open a new position with slippage.
unchecked {
openAmount = currentPosition.amountInBips + int104(amountInBips);
usdnBalance += currentUSDNPnlInBips / _BIPS;
}
// Apply profits to user's balance and emit event to represent close with no slippage.
tournament.userBalance[trader] = usdnBalance;
emit TradeExecuted(
tournamentId, nonce, trader, currentPosition.amountInBips * -1, normalizedPrice, pair
);
// Slippage is applied only to the new position.
int256 newTradeSizeInBips;
unchecked {
newTradeSizeInBips = openAmount * normalizedPrice;
}
int256 executionPrice =
_calculateEntryPriceWithSlippageApplied(_abs(newTradeSizeInBips), normalizedPrice, direction);
tournament.activeTrade[trader] =
Trade(pair, uint40(block.timestamp), int104(executionPrice), openAmount);
emit TradeExecuted(tournamentId, nonce, trader, openAmount, executionPrice, pair);
emit AccountUpdated(
tournamentId, trader, tournament.userBalance[trader], openAmount, executionPrice, pair
);
}
} else {
// Position is being opened or increased. Both are commendable actions.
int256 executionPrice = _calculateEntryPriceWithSlippageApplied(tradeSizeInBips, normalizedPrice, direction);
int256 entryPrice;
unchecked {
// Scale position size up and calculate new entry price as weighted average of current and new trade.
openAmount = currentPosition.amountInBips + int104(amountInBips);
entryPrice = (
currentPosition.entryPrice * absoluteActivePositionAmountInBips
+ executionPrice * absoluteAmountInBips
) / (absoluteActivePositionAmountInBips + absoluteAmountInBips);
}
tournament.activeTrade[trader] = Trade(pair, uint40(block.timestamp), int104(entryPrice), openAmount);
emit TradeExecuted(tournamentId, nonce, trader, amountInBips, executionPrice, pair);
emit AccountUpdated(tournamentId, trader, tournament.userBalance[trader], openAmount, entryPrice, pair);
}
}
/**
* @notice Liquidates a user in a tournament, as long as their balance + pnl is negative.
* @param tournamentId The ID of the tournament.
* @param users The users to liquidate.
* @param priceUpdates The Pyth price update data.
*/
function liquidate(uint256 tournamentId, address[] calldata users, bytes[] calldata priceUpdates) external {
TournamentConfig storage tournament = tournaments[tournamentId];
_assertTournamentOpen(tournament);
updatePythPriceFeeds(priceUpdates);
uint256 liquidationBountyPayout;
for (uint256 i = 0; i < users.length; i++) {
int256 currentPnl = _currentPnlFor(tournament, users[i]);
if (currentPnl + tournament.userBalance[users[i]] < 0) {
// Get rekt noob.
tournament.userBalance[users[i]] = 0;
delete tournament.activeTrade[users[i]];
unchecked {
liquidationBountyPayout += liquidationBounty;
}
emit Liquidated(tournamentId, users[i]);
} else {
// You're safe for now.
emit InvalidLiquidation(tournamentId, users[i], tournament.userBalance[users[i]], currentPnl);
}
}
if (liquidationBountyPayout > 0) {
(bool success,) = msg.sender.call{value: liquidationBountyPayout}("");
if (!success) revert TransferFailed();
emit LiquidationBountyPaid(msg.sender, liquidationBountyPayout);
}
}
/**
* @notice Manual price update.
* @param priceUpdates The Pyth price update data.
*/
function updatePythPriceFeeds(bytes[] calldata priceUpdates) public {
if (priceUpdates.length > 0) {
uint256 fee = _PYTH.getUpdateFee(priceUpdates);
_PYTH.updatePriceFeeds{value: fee}(priceUpdates);
}
}
/**
* @notice Manual price update.
* @param pairs The pairs to update.
* @param prices The prices to update.
*/
function updateManualPriceFeeds(bytes32[] calldata pairs, InternalPrice[] calldata prices) external onlyRoles(_PRICE_MANAGER) {
if (pairs.length != prices.length) revert ArrayLengthMismatch();
for (uint256 i = 0; i < pairs.length; i++) {
InternalPrice memory price = prices[i];
bytes32 pair = pairs[i];
if (!_isManualPriceOverride(pair)) revert InvalidPriceUpdates();
// disallow future timestamps.
if (price.publishTime > block.timestamp) revert InvalidTimestamp();
if (price.publishTime <= internalPrices[pair].publishTime) continue;
internalPrices[pair] = price;
}
}
/**
* @notice Finalize prices for a tournament.
* @param tournamentId The ID of the tournament.
* @param priceUpdates The Pyth price update data.
* @param pairs The pairs to finalize prices for.
*/
function finalizePrices(uint256 tournamentId, bytes[] calldata priceUpdates, bytes32[] calldata pairs)
external
onlyRoles(_TOURNAMENT_MANAGER)
{
TournamentConfig storage tournament = tournaments[tournamentId];
if (tournament.endTime > block.timestamp) revert TournamentActive();
uint256 fee = _PYTH.getUpdateFee(priceUpdates);
PythStructs.PriceFeed[] memory priceFeeds = _PYTH.parsePriceFeedUpdates{value: fee}(
priceUpdates, pairs, tournament.endTime, tournament.endTime + _FINAL_ENDTIME_GRACE_PERIOD
);
for (uint256 i = 0; i < priceFeeds.length; i++) {
PythStructs.Price memory price = priceFeeds[i].price;
// convert to InternalPrice to save a storage slot.
tournament.finalPrices[priceFeeds[i].id] =
InternalPrice({price: price.price, expo: price.expo, publishTime: uint40(price.publishTime)});
}
}
/**
* @notice Sets the counterparty signer.
* @param signer The new counterparty signer.
*/
function setCounterpartySigner(address signer) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
_setCounterpartySigner(signer);
}
/**
* @notice Sets the liquidation bounty.
* @param bounty The new liquidation bounty.
*/
function setLiquidationBounty(uint256 bounty) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
_setLiquidationBounty(bounty);
}
/**
* @notice Returns the final pyth price for a pair in a tournament.
* @param tournamentId The ID of the tournament.
* @param pair The pair to get final pyth price for.
*/
function finalPrice(uint256 tournamentId, bytes32 pair) external view returns (InternalPrice memory) {
return tournaments[tournamentId].finalPrices[pair];
}
/**
* @notice Sets the base slippage scaling factor.
* @param factor The new base slippage scaling factor.
*/
function setBaseSlippageScalingFactor(uint256 factor) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
_setBaseSlippageScalingFactor(factor);
}
/**
* @notice Sets the slippage increment scaling factor.
* @param factor The new slippage increment scaling factor.
*/
function setSlippageIncrementScalingFactor(uint256 factor) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
_setSlippageIncrementScalingFactor(factor);
}
/**
* @notice Submit results for a tournament.
* @param tournamentId The ID of the tournament.
* @param results The results to propose.
*/
function submitResults(uint256 tournamentId, IRebootTournament.Result[] calldata results)
external
onlyRoles(_TOURNAMENT_MANAGER)
{
_REBOOT.submitResults(tournamentId, 0, results);
}
function cleanup(uint256 tournamentId) external onlyRoles(_TOURNAMENT_MANAGER) {
_REBOOT.cleanup(tournamentId, 0);
}
/**
* @notice Withdraws the contract balance.
* @dev If there is a liquidation bounty in place, calling this while a tournament is active will cause liquidations to fail.
*/
function withdraw() external onlyOwner {
(bool success,) = msg.sender.call{value: address(this).balance}("");
if (!success) revert TransferFailed();
}
/**
* @notice Returns the most recent Reboot tournament ID.
*/
function currentTournamentId() external view returns (uint256) {
return _REBOOT.tournamentCount();
}
/**
* @notice Returns the active trade for a user in a tournament.
* @param tournamentId The ID of the tournament.
* @param user The address of the user.
*/
function activeTrade(uint256 tournamentId, address user) external view returns (Trade memory) {
return tournaments[tournamentId].activeTrade[user];
}
/**
* .
* @notice Returns the balance for a user in a tournament.
* @param tournamentId The ID of the tournament.
* @param user The address of the user.
*/
function userBalance(uint256 tournamentId, address user) external view returns (int256) {
return tournaments[tournamentId].userBalance[user];
}
/**
* @notice Returns the current UPnL for a user in a tournament.
* @param tournamentId The ID of the tournament.
* @param user The address of the user.
* @dev UPnL does not include the user's balance.
* @dev Does not perform a price update, so may be stale. Use updatePrices to ensure the most recent prices.
* @dev accounts for slippage on close.
*/
function currentPnlFor(uint256 tournamentId, address user) external view returns (int256) {
TournamentConfig storage tournament = tournaments[tournamentId];
return _currentPnlFor(tournament, user);
}
/**
* @notice Enters a user into a tournament.
* @param tournamentId The ID of the tournament.
*/
function _enterTournament(address user, uint256 tournamentId) internal {
TournamentConfig storage tournament = tournaments[tournamentId];
if (tournament.userBalance[user] != 0) {
revert AlreadyEntered();
}
_assertTournamentOpen(tournament);
// A valid underflow is incredibly impractical.
uint256 valueToTransfer;
unchecked {
valueToTransfer = msg.value - liquidationBounty;
}
uint256 entryFee = _REBOOT.getTournament(tournamentId).entryFee;
// If msg.sender == user, they can use their reboot credits to enter. If not, msg.value must be used for entry fee.
if (msg.sender != user && valueToTransfer < entryFee) revert InsufficientEntryFee();
_REBOOT.enter{value: valueToTransfer}(
IRebootTournament.EntryParams(user, tournamentId, 0, address(0), 0, entryFee)
);
tournament.userBalance[user] = int256(int128(tournament.startBalance));
}
function _setCounterpartySigner(address signer) internal {
if (signer == address(0)) revert ZeroAddress();
counterpartySigner = signer;
emit CounterpartySignerUpdated(signer);
}
function _setBaseSlippageScalingFactor(uint256 factor) internal {
// revert if slippage scaling factor > _MAX_BASE_SLIPPAGE_SCALING_FACTOR.
if (factor > _MAX_BASE_SLIPPAGE_SCALING_FACTOR) revert SlippageTooHigh();
baseSlippageScalingFactor = int256(factor);
emit BaseSlippageScalingFactorUpdated(factor);
}
function _setSlippageIncrementScalingFactor(uint256 factor) internal {
// revert if slippage scaling factor > _MAX_SLIPPAGE_INCREMENT_SCALING_FACTOR.
if (factor > _MAX_SLIPPAGE_INCREMENT_SCALING_FACTOR) revert SlippageTooHigh();
slippageIncrementScalingFactor = int256(factor);
emit SlippageScalingFactorUpdated(factor);
}
function _setLiquidationBounty(uint256 bounty) internal {
// bounty should typically be ~1 apecoin or less.
if (bounty > 1_000 ether) revert LiquidationBountyTooHigh();
liquidationBounty = bounty;
emit LiquidationBountyUpdated(bounty);
}
function _updateAndFetchPythPrice(bytes32 pair, bytes[] calldata priceUpdates)
internal
returns (PythStructs.Price memory)
{
updatePythPriceFeeds(priceUpdates);
return _PYTH.getPrice(pair);
}
function _fetchAndNormalizePrice(bytes32 pair) internal view returns (int256) {
if (bytes4(pair) == _MANUAL_PRICE_PAIR_IDENTIFIER) {
InternalPrice memory price = internalPrices[pair];
if (price.publishTime < block.timestamp - _PYTH.getValidTimePeriod()) {
revert StalePrice();
}
return _normalizePrice(price.price, price.expo);
} else {
PythStructs.Price memory price = _PYTH.getPrice(pair);
return _normalizePrice(price.price, price.expo);
}
}
function _currentPnlFor(TournamentConfig storage tournament, address user) internal view returns (int256) {
Trade memory activePosition = tournament.activeTrade[user];
bytes32 pair = activePosition.pair;
if (pair == 0x0) {
return 0;
}
int256 normalizedPrice;
if (tournament.finalPrices[pair].price != 0) {
InternalPrice memory price = tournament.finalPrices[pair];
normalizedPrice = _normalizePrice(price.price, price.expo);
} else {
normalizedPrice = _fetchAndNormalizePrice(pair);
}
return _currentPnlForPositionAndPrice(activePosition, normalizedPrice) / _BIPS;
}
function _assertTournamentOpen(TournamentConfig storage tournament) internal view {
if (block.timestamp < tournament.startTime || block.timestamp > tournament.endTime) {
revert NotActive();
}
}
// No free rides.
function _calculateEntryPriceWithSlippageApplied(int256 tradeSizeInBips, int256 rawEntryPrice, Direction direction)
internal
view
returns (int256 executionPrice)
{
int256 floorTradeSize;
unchecked {
floorTradeSize = tradeSizeInBips / (_BIPS * 1e18);
}
int256 directionModifier = direction == Direction.LONG ? int256(1) : int256(-1);
if (floorTradeSize < _FIRST_SLIPPAGE_BREAKPOINT) {
// Apply 1x base slippage to raw entry price.
unchecked {
int256 slippage = rawEntryPrice * baseSlippageScalingFactor / 1_000_000;
executionPrice = directionModifier * slippage + rawEntryPrice;
}
} else if (floorTradeSize < _SECOND_SLIPPAGE_BREAKPOINT) {
// Apply 2x base slippage to raw entry price.
unchecked {
int256 slippage = rawEntryPrice * baseSlippageScalingFactor / 500_000;
executionPrice = directionModifier * slippage + rawEntryPrice;
}
} else {
// Apply 2x base slippage + additional slippage for every order of magnitude increase.
int256 additionalSlippage = 0;
unchecked {
int256 baseSlippage = rawEntryPrice * baseSlippageScalingFactor / 500_000;
int256 slippageIncrement = rawEntryPrice * slippageIncrementScalingFactor / 10_000_000;
while (floorTradeSize >= _SECOND_SLIPPAGE_BREAKPOINT) {
additionalSlippage += slippageIncrement;
floorTradeSize = floorTradeSize * 7943 / 10000; // Reduce by 1/10th of a magnitude (20.57%).
}
int256 totalSlippage = baseSlippage + additionalSlippage;
executionPrice = directionModifier * totalSlippage + rawEntryPrice;
}
}
}
function _normalizePrice(int64 price, int32 expo) internal pure returns (int256) {
unchecked {
if (expo < -18) {
return int256(price) / int256(10 ** uint256(uint32(-(18 + expo))));
} else {
return int256(price) * int256(10 ** uint256(uint32(18 + expo)));
}
}
}
function _currentPnlForPositionAndPrice(Trade memory activePosition, int256 normalizedPrice)
internal
pure
returns (int256)
{
unchecked {
return (normalizedPrice - activePosition.entryPrice) * activePosition.amountInBips;
}
}
function _isManualPriceOverride(bytes32 pair) internal pure returns (bool) {
return bytes4(pair) == _MANUAL_PRICE_PAIR_IDENTIFIER;
}
function _abs(int256 x) internal pure returns (int256) {
return x >= 0 ? x : -x;
}
}
/**
* _ _ _______ _______ _____ _______ _____ _______ _ _ _____ __ _ ______
* |____/ |______ |______ |_____] | | | | |____/ | | \ | | ____
* | \_ |______ |______ | |_____ |_____ __|__ |_____ | \_ __|__ | \_| |_____| .
*
* ______ _____ ______ __ __ _______ _ _ _ _______ _____ _______ _______
* | ____ | | | |_____/ \_/ |_____| | | | |_____| | | |______
* |_____| |_____ |_____| | \_ | | | |__|__| | | __|__ | ______| .
*
* ______ _______ _______ _______ _____ ______ _______
* |_____] |______ | | | | | | |_____/ |______
* |_____] |______ | | | | |_____| | \_ |______ .
*/
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import "./PythStructs.sol";
import "./IPythEvents.sol";
/// @title Consume prices from the Pyth Network (https://pyth.network/).
/// @dev Please refer to the guidance at https://docs.pyth.network/consumers/best-practices for how to consume prices safely.
/// @author Pyth Data Association
interface IPyth is IPythEvents {
/// @notice Returns the period (in seconds) that a price feed is considered valid since its publish time
function getValidTimePeriod() external view returns (uint validTimePeriod);
/// @notice Returns the price and confidence interval.
/// @dev Reverts if the price has not been updated within the last `getValidTimePeriod()` seconds.
/// @param id The Pyth Price Feed ID of which to fetch the price and confidence interval.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getPrice(
bytes32 id
) external view returns (PythStructs.Price memory price);
/// @notice Returns the exponentially-weighted moving average price and confidence interval.
/// @dev Reverts if the EMA price is not available.
/// @param id The Pyth Price Feed ID of which to fetch the EMA price and confidence interval.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getEmaPrice(
bytes32 id
) external view returns (PythStructs.Price memory price);
/// @notice Returns the price of a price feed without any sanity checks.
/// @dev This function returns the most recent price update in this contract without any recency checks.
/// This function is unsafe as the returned price update may be arbitrarily far in the past.
///
/// Users of this function should check the `publishTime` in the price to ensure that the returned price is
/// sufficiently recent for their application. If you are considering using this function, it may be
/// safer / easier to use either `getPrice` or `getPriceNoOlderThan`.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getPriceUnsafe(
bytes32 id
) external view returns (PythStructs.Price memory price);
/// @notice Returns the price that is no older than `age` seconds of the current time.
/// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in
/// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently
/// recently.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getPriceNoOlderThan(
bytes32 id,
uint age
) external view returns (PythStructs.Price memory price);
/// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks.
/// @dev This function returns the same price as `getEmaPrice` in the case where the price is available.
/// However, if the price is not recent this function returns the latest available price.
///
/// The returned price can be from arbitrarily far in the past; this function makes no guarantees that
/// the returned price is recent or useful for any particular application.
///
/// Users of this function should check the `publishTime` in the price to ensure that the returned price is
/// sufficiently recent for their application. If you are considering using this function, it may be
/// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getEmaPriceUnsafe(
bytes32 id
) external view returns (PythStructs.Price memory price);
/// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds
/// of the current time.
/// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in
/// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently
/// recently.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getEmaPriceNoOlderThan(
bytes32 id,
uint age
) external view returns (PythStructs.Price memory price);
/// @notice Update price feeds with given update messages.
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
/// `getUpdateFee` with the length of the `updateData` array.
/// Prices will be updated if they are more recent than the current stored prices.
/// The call will succeed even if the update is not the most recent.
/// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid.
/// @param updateData Array of price update data.
function updatePriceFeeds(bytes[] calldata updateData) external payable;
/// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is
/// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the
/// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`.
///
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
/// `getUpdateFee` with the length of the `updateData` array.
///
/// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime
/// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have
/// a newer or equal publish time than the given publish time, it will reject the transaction to save gas.
/// Otherwise, it calls updatePriceFeeds method to update the prices.
///
/// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid.
/// @param updateData Array of price update data.
/// @param priceIds Array of price ids.
/// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]`
function updatePriceFeedsIfNecessary(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64[] calldata publishTimes
) external payable;
/// @notice Returns the required fee to update an array of price updates.
/// @param updateData Array of price update data.
/// @return feeAmount The required fee in Wei.
function getUpdateFee(
bytes[] calldata updateData
) external view returns (uint feeAmount);
/// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published
/// within `minPublishTime` and `maxPublishTime`.
///
/// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price;
/// otherwise, please consider using `updatePriceFeeds`. This method does not store the price updates on-chain.
///
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
/// `getUpdateFee` with the length of the `updateData` array.
///
///
/// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is
/// no update for any of the given `priceIds` within the given time range.
/// @param updateData Array of price update data.
/// @param priceIds Array of price ids.
/// @param minPublishTime minimum acceptable publishTime for the given `priceIds`.
/// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`.
/// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order).
function parsePriceFeedUpdates(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64 minPublishTime,
uint64 maxPublishTime
) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
contract PythStructs {
// A price with a degree of uncertainty, represented as a price +- a confidence interval.
//
// The confidence interval roughly corresponds to the standard error of a normal distribution.
// Both the price and confidence are stored in a fixed-point numeric representation,
// `x * (10^expo)`, where `expo` is the exponent.
//
// Please refer to the documentation at https://docs.pyth.network/consumers/best-practices for how
// to how this price safely.
struct Price {
// Price
int64 price;
// Confidence interval around the price
uint64 conf;
// Price exponent
int32 expo;
// Unix timestamp describing when the price was published
uint publishTime;
}
// PriceFeed represents a current aggregate price from pyth publisher feeds.
struct PriceFeed {
// The price ID.
bytes32 id;
// Latest available price
Price price;
// Latest available exponentially-weighted moving average price
Price emaPrice;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Ownable} from "./Ownable.sol";
/// @notice Simple single owner and multiroles authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
/// @dev While the ownable portion follows [EIP-173](https://eips.ethereum.org/EIPS/eip-173)
/// for compatibility, the nomenclature for the 2-step ownership handover and roles
/// may be unique to this codebase.
abstract contract OwnableRoles is Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The `user`'s roles is updated to `roles`.
/// Each bit of `roles` represents whether the role is set.
event RolesUpdated(address indexed user, uint256 indexed roles);
/// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`.
uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE =
0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The role slot of `user` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED))
/// let roleSlot := keccak256(0x00, 0x20)
/// ```
/// This automatically ignores the upper bits of the `user` in case
/// they are not clean, as well as keep the `keccak256` under 32-bytes.
///
/// Note: This is equivalent to `uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))`.
uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Overwrite the roles directly without authorization guard.
function _setRoles(address user, uint256 roles) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Store the new value.
sstore(keccak256(0x0c, 0x20), roles)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles)
}
}
/// @dev Updates the roles directly without authorization guard.
/// If `on` is true, each set bit of `roles` will be turned on,
/// otherwise, each set bit of `roles` will be turned off.
function _updateRoles(address user, uint256 roles, bool on) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
let roleSlot := keccak256(0x0c, 0x20)
// Load the current value.
let current := sload(roleSlot)
// Compute the updated roles if `on` is true.
let updated := or(current, roles)
// Compute the updated roles if `on` is false.
// Use `and` to compute the intersection of `current` and `roles`,
// `xor` it with `current` to flip the bits in the intersection.
if iszero(on) { updated := xor(current, and(current, roles)) }
// Then, store the new value.
sstore(roleSlot, updated)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated)
}
}
/// @dev Grants the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn on.
function _grantRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, true);
}
/// @dev Removes the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn off.
function _removeRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, false);
}
/// @dev Throws if the sender does not have any of the `roles`.
function _checkRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Throws if the sender is not the owner,
/// and does not have any of the `roles`.
/// Checks for ownership first, then lazily checks for roles.
function _checkOwnerOrRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Throws if the sender does not have any of the `roles`,
/// and is not the owner.
/// Checks for roles first, then lazily checks for ownership.
function _checkRolesOrOwner(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } {
// We don't need to mask the values of `ordinals`, as Solidity
// cleans dirty upper bits when storing variables into memory.
roles := or(shl(mload(add(ordinals, i)), 1), roles)
}
}
}
/// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) {
/// @solidity memory-safe-assembly
assembly {
// Grab the pointer to the free memory.
ordinals := mload(0x40)
let ptr := add(ordinals, 0x20)
let o := 0
// The absence of lookup tables, De Bruijn, etc., here is intentional for
// smaller bytecode, as this function is not meant to be called on-chain.
for { let t := roles } 1 {} {
mstore(ptr, o)
// `shr` 5 is equivalent to multiplying by 0x20.
// Push back into the ordinals array if the bit is set.
ptr := add(ptr, shl(5, and(t, 1)))
o := add(o, 1)
t := shr(o, roles)
if iszero(t) { break }
}
// Store the length of `ordinals`.
mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20))))
// Allocate the memory.
mstore(0x40, ptr)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to grant `user` `roles`.
/// If the `user` already has a role, then it will be an no-op for the role.
function grantRoles(address user, uint256 roles) public payable virtual onlyOwner {
_grantRoles(user, roles);
}
/// @dev Allows the owner to remove `user` `roles`.
/// If the `user` does not have a role, then it will be an no-op for the role.
function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner {
_removeRoles(user, roles);
}
/// @dev Allow the caller to remove their own roles.
/// If the caller does not have a role, then it will be an no-op for the role.
function renounceRoles(uint256 roles) public payable virtual {
_removeRoles(msg.sender, roles);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the roles of `user`.
function rolesOf(address user) public view virtual returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Load the stored value.
roles := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns whether `user` has any of `roles`.
function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles != 0;
}
/// @dev Returns whether `user` has all of `roles`.
function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles == roles;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by an account with `roles`.
modifier onlyRoles(uint256 roles) virtual {
_checkRoles(roles);
_;
}
/// @dev Marks a function as only callable by the owner or by an account
/// with `roles`. Checks for ownership first, then lazily checks for roles.
modifier onlyOwnerOrRoles(uint256 roles) virtual {
_checkOwnerOrRoles(roles);
_;
}
/// @dev Marks a function as only callable by an account with `roles`
/// or the owner. Checks for roles first, then lazily checks for ownership.
modifier onlyRolesOrOwner(uint256 roles) virtual {
_checkRolesOrOwner(roles);
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ROLE CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// IYKYK
uint256 internal constant _ROLE_0 = 1 << 0;
uint256 internal constant _ROLE_1 = 1 << 1;
uint256 internal constant _ROLE_2 = 1 << 2;
uint256 internal constant _ROLE_3 = 1 << 3;
uint256 internal constant _ROLE_4 = 1 << 4;
uint256 internal constant _ROLE_5 = 1 << 5;
uint256 internal constant _ROLE_6 = 1 << 6;
uint256 internal constant _ROLE_7 = 1 << 7;
uint256 internal constant _ROLE_8 = 1 << 8;
uint256 internal constant _ROLE_9 = 1 << 9;
uint256 internal constant _ROLE_10 = 1 << 10;
uint256 internal constant _ROLE_11 = 1 << 11;
uint256 internal constant _ROLE_12 = 1 << 12;
uint256 internal constant _ROLE_13 = 1 << 13;
uint256 internal constant _ROLE_14 = 1 << 14;
uint256 internal constant _ROLE_15 = 1 << 15;
uint256 internal constant _ROLE_16 = 1 << 16;
uint256 internal constant _ROLE_17 = 1 << 17;
uint256 internal constant _ROLE_18 = 1 << 18;
uint256 internal constant _ROLE_19 = 1 << 19;
uint256 internal constant _ROLE_20 = 1 << 20;
uint256 internal constant _ROLE_21 = 1 << 21;
uint256 internal constant _ROLE_22 = 1 << 22;
uint256 internal constant _ROLE_23 = 1 << 23;
uint256 internal constant _ROLE_24 = 1 << 24;
uint256 internal constant _ROLE_25 = 1 << 25;
uint256 internal constant _ROLE_26 = 1 << 26;
uint256 internal constant _ROLE_27 = 1 << 27;
uint256 internal constant _ROLE_28 = 1 << 28;
uint256 internal constant _ROLE_29 = 1 << 29;
uint256 internal constant _ROLE_30 = 1 << 30;
uint256 internal constant _ROLE_31 = 1 << 31;
uint256 internal constant _ROLE_32 = 1 << 32;
uint256 internal constant _ROLE_33 = 1 << 33;
uint256 internal constant _ROLE_34 = 1 << 34;
uint256 internal constant _ROLE_35 = 1 << 35;
uint256 internal constant _ROLE_36 = 1 << 36;
uint256 internal constant _ROLE_37 = 1 << 37;
uint256 internal constant _ROLE_38 = 1 << 38;
uint256 internal constant _ROLE_39 = 1 << 39;
uint256 internal constant _ROLE_40 = 1 << 40;
uint256 internal constant _ROLE_41 = 1 << 41;
uint256 internal constant _ROLE_42 = 1 << 42;
uint256 internal constant _ROLE_43 = 1 << 43;
uint256 internal constant _ROLE_44 = 1 << 44;
uint256 internal constant _ROLE_45 = 1 << 45;
uint256 internal constant _ROLE_46 = 1 << 46;
uint256 internal constant _ROLE_47 = 1 << 47;
uint256 internal constant _ROLE_48 = 1 << 48;
uint256 internal constant _ROLE_49 = 1 << 49;
uint256 internal constant _ROLE_50 = 1 << 50;
uint256 internal constant _ROLE_51 = 1 << 51;
uint256 internal constant _ROLE_52 = 1 << 52;
uint256 internal constant _ROLE_53 = 1 << 53;
uint256 internal constant _ROLE_54 = 1 << 54;
uint256 internal constant _ROLE_55 = 1 << 55;
uint256 internal constant _ROLE_56 = 1 << 56;
uint256 internal constant _ROLE_57 = 1 << 57;
uint256 internal constant _ROLE_58 = 1 << 58;
uint256 internal constant _ROLE_59 = 1 << 59;
uint256 internal constant _ROLE_60 = 1 << 60;
uint256 internal constant _ROLE_61 = 1 << 61;
uint256 internal constant _ROLE_62 = 1 << 62;
uint256 internal constant _ROLE_63 = 1 << 63;
uint256 internal constant _ROLE_64 = 1 << 64;
uint256 internal constant _ROLE_65 = 1 << 65;
uint256 internal constant _ROLE_66 = 1 << 66;
uint256 internal constant _ROLE_67 = 1 << 67;
uint256 internal constant _ROLE_68 = 1 << 68;
uint256 internal constant _ROLE_69 = 1 << 69;
uint256 internal constant _ROLE_70 = 1 << 70;
uint256 internal constant _ROLE_71 = 1 << 71;
uint256 internal constant _ROLE_72 = 1 << 72;
uint256 internal constant _ROLE_73 = 1 << 73;
uint256 internal constant _ROLE_74 = 1 << 74;
uint256 internal constant _ROLE_75 = 1 << 75;
uint256 internal constant _ROLE_76 = 1 << 76;
uint256 internal constant _ROLE_77 = 1 << 77;
uint256 internal constant _ROLE_78 = 1 << 78;
uint256 internal constant _ROLE_79 = 1 << 79;
uint256 internal constant _ROLE_80 = 1 << 80;
uint256 internal constant _ROLE_81 = 1 << 81;
uint256 internal constant _ROLE_82 = 1 << 82;
uint256 internal constant _ROLE_83 = 1 << 83;
uint256 internal constant _ROLE_84 = 1 << 84;
uint256 internal constant _ROLE_85 = 1 << 85;
uint256 internal constant _ROLE_86 = 1 << 86;
uint256 internal constant _ROLE_87 = 1 << 87;
uint256 internal constant _ROLE_88 = 1 << 88;
uint256 internal constant _ROLE_89 = 1 << 89;
uint256 internal constant _ROLE_90 = 1 << 90;
uint256 internal constant _ROLE_91 = 1 << 91;
uint256 internal constant _ROLE_92 = 1 << 92;
uint256 internal constant _ROLE_93 = 1 << 93;
uint256 internal constant _ROLE_94 = 1 << 94;
uint256 internal constant _ROLE_95 = 1 << 95;
uint256 internal constant _ROLE_96 = 1 << 96;
uint256 internal constant _ROLE_97 = 1 << 97;
uint256 internal constant _ROLE_98 = 1 << 98;
uint256 internal constant _ROLE_99 = 1 << 99;
uint256 internal constant _ROLE_100 = 1 << 100;
uint256 internal constant _ROLE_101 = 1 << 101;
uint256 internal constant _ROLE_102 = 1 << 102;
uint256 internal constant _ROLE_103 = 1 << 103;
uint256 internal constant _ROLE_104 = 1 << 104;
uint256 internal constant _ROLE_105 = 1 << 105;
uint256 internal constant _ROLE_106 = 1 << 106;
uint256 internal constant _ROLE_107 = 1 << 107;
uint256 internal constant _ROLE_108 = 1 << 108;
uint256 internal constant _ROLE_109 = 1 << 109;
uint256 internal constant _ROLE_110 = 1 << 110;
uint256 internal constant _ROLE_111 = 1 << 111;
uint256 internal constant _ROLE_112 = 1 << 112;
uint256 internal constant _ROLE_113 = 1 << 113;
uint256 internal constant _ROLE_114 = 1 << 114;
uint256 internal constant _ROLE_115 = 1 << 115;
uint256 internal constant _ROLE_116 = 1 << 116;
uint256 internal constant _ROLE_117 = 1 << 117;
uint256 internal constant _ROLE_118 = 1 << 118;
uint256 internal constant _ROLE_119 = 1 << 119;
uint256 internal constant _ROLE_120 = 1 << 120;
uint256 internal constant _ROLE_121 = 1 << 121;
uint256 internal constant _ROLE_122 = 1 << 122;
uint256 internal constant _ROLE_123 = 1 << 123;
uint256 internal constant _ROLE_124 = 1 << 124;
uint256 internal constant _ROLE_125 = 1 << 125;
uint256 internal constant _ROLE_126 = 1 << 126;
uint256 internal constant _ROLE_127 = 1 << 127;
uint256 internal constant _ROLE_128 = 1 << 128;
uint256 internal constant _ROLE_129 = 1 << 129;
uint256 internal constant _ROLE_130 = 1 << 130;
uint256 internal constant _ROLE_131 = 1 << 131;
uint256 internal constant _ROLE_132 = 1 << 132;
uint256 internal constant _ROLE_133 = 1 << 133;
uint256 internal constant _ROLE_134 = 1 << 134;
uint256 internal constant _ROLE_135 = 1 << 135;
uint256 internal constant _ROLE_136 = 1 << 136;
uint256 internal constant _ROLE_137 = 1 << 137;
uint256 internal constant _ROLE_138 = 1 << 138;
uint256 internal constant _ROLE_139 = 1 << 139;
uint256 internal constant _ROLE_140 = 1 << 140;
uint256 internal constant _ROLE_141 = 1 << 141;
uint256 internal constant _ROLE_142 = 1 << 142;
uint256 internal constant _ROLE_143 = 1 << 143;
uint256 internal constant _ROLE_144 = 1 << 144;
uint256 internal constant _ROLE_145 = 1 << 145;
uint256 internal constant _ROLE_146 = 1 << 146;
uint256 internal constant _ROLE_147 = 1 << 147;
uint256 internal constant _ROLE_148 = 1 << 148;
uint256 internal constant _ROLE_149 = 1 << 149;
uint256 internal constant _ROLE_150 = 1 << 150;
uint256 internal constant _ROLE_151 = 1 << 151;
uint256 internal constant _ROLE_152 = 1 << 152;
uint256 internal constant _ROLE_153 = 1 << 153;
uint256 internal constant _ROLE_154 = 1 << 154;
uint256 internal constant _ROLE_155 = 1 << 155;
uint256 internal constant _ROLE_156 = 1 << 156;
uint256 internal constant _ROLE_157 = 1 << 157;
uint256 internal constant _ROLE_158 = 1 << 158;
uint256 internal constant _ROLE_159 = 1 << 159;
uint256 internal constant _ROLE_160 = 1 << 160;
uint256 internal constant _ROLE_161 = 1 << 161;
uint256 internal constant _ROLE_162 = 1 << 162;
uint256 internal constant _ROLE_163 = 1 << 163;
uint256 internal constant _ROLE_164 = 1 << 164;
uint256 internal constant _ROLE_165 = 1 << 165;
uint256 internal constant _ROLE_166 = 1 << 166;
uint256 internal constant _ROLE_167 = 1 << 167;
uint256 internal constant _ROLE_168 = 1 << 168;
uint256 internal constant _ROLE_169 = 1 << 169;
uint256 internal constant _ROLE_170 = 1 << 170;
uint256 internal constant _ROLE_171 = 1 << 171;
uint256 internal constant _ROLE_172 = 1 << 172;
uint256 internal constant _ROLE_173 = 1 << 173;
uint256 internal constant _ROLE_174 = 1 << 174;
uint256 internal constant _ROLE_175 = 1 << 175;
uint256 internal constant _ROLE_176 = 1 << 176;
uint256 internal constant _ROLE_177 = 1 << 177;
uint256 internal constant _ROLE_178 = 1 << 178;
uint256 internal constant _ROLE_179 = 1 << 179;
uint256 internal constant _ROLE_180 = 1 << 180;
uint256 internal constant _ROLE_181 = 1 << 181;
uint256 internal constant _ROLE_182 = 1 << 182;
uint256 internal constant _ROLE_183 = 1 << 183;
uint256 internal constant _ROLE_184 = 1 << 184;
uint256 internal constant _ROLE_185 = 1 << 185;
uint256 internal constant _ROLE_186 = 1 << 186;
uint256 internal constant _ROLE_187 = 1 << 187;
uint256 internal constant _ROLE_188 = 1 << 188;
uint256 internal constant _ROLE_189 = 1 << 189;
uint256 internal constant _ROLE_190 = 1 << 190;
uint256 internal constant _ROLE_191 = 1 << 191;
uint256 internal constant _ROLE_192 = 1 << 192;
uint256 internal constant _ROLE_193 = 1 << 193;
uint256 internal constant _ROLE_194 = 1 << 194;
uint256 internal constant _ROLE_195 = 1 << 195;
uint256 internal constant _ROLE_196 = 1 << 196;
uint256 internal constant _ROLE_197 = 1 << 197;
uint256 internal constant _ROLE_198 = 1 << 198;
uint256 internal constant _ROLE_199 = 1 << 199;
uint256 internal constant _ROLE_200 = 1 << 200;
uint256 internal constant _ROLE_201 = 1 << 201;
uint256 internal constant _ROLE_202 = 1 << 202;
uint256 internal constant _ROLE_203 = 1 << 203;
uint256 internal constant _ROLE_204 = 1 << 204;
uint256 internal constant _ROLE_205 = 1 << 205;
uint256 internal constant _ROLE_206 = 1 << 206;
uint256 internal constant _ROLE_207 = 1 << 207;
uint256 internal constant _ROLE_208 = 1 << 208;
uint256 internal constant _ROLE_209 = 1 << 209;
uint256 internal constant _ROLE_210 = 1 << 210;
uint256 internal constant _ROLE_211 = 1 << 211;
uint256 internal constant _ROLE_212 = 1 << 212;
uint256 internal constant _ROLE_213 = 1 << 213;
uint256 internal constant _ROLE_214 = 1 << 214;
uint256 internal constant _ROLE_215 = 1 << 215;
uint256 internal constant _ROLE_216 = 1 << 216;
uint256 internal constant _ROLE_217 = 1 << 217;
uint256 internal constant _ROLE_218 = 1 << 218;
uint256 internal constant _ROLE_219 = 1 << 219;
uint256 internal constant _ROLE_220 = 1 << 220;
uint256 internal constant _ROLE_221 = 1 << 221;
uint256 internal constant _ROLE_222 = 1 << 222;
uint256 internal constant _ROLE_223 = 1 << 223;
uint256 internal constant _ROLE_224 = 1 << 224;
uint256 internal constant _ROLE_225 = 1 << 225;
uint256 internal constant _ROLE_226 = 1 << 226;
uint256 internal constant _ROLE_227 = 1 << 227;
uint256 internal constant _ROLE_228 = 1 << 228;
uint256 internal constant _ROLE_229 = 1 << 229;
uint256 internal constant _ROLE_230 = 1 << 230;
uint256 internal constant _ROLE_231 = 1 << 231;
uint256 internal constant _ROLE_232 = 1 << 232;
uint256 internal constant _ROLE_233 = 1 << 233;
uint256 internal constant _ROLE_234 = 1 << 234;
uint256 internal constant _ROLE_235 = 1 << 235;
uint256 internal constant _ROLE_236 = 1 << 236;
uint256 internal constant _ROLE_237 = 1 << 237;
uint256 internal constant _ROLE_238 = 1 << 238;
uint256 internal constant _ROLE_239 = 1 << 239;
uint256 internal constant _ROLE_240 = 1 << 240;
uint256 internal constant _ROLE_241 = 1 << 241;
uint256 internal constant _ROLE_242 = 1 << 242;
uint256 internal constant _ROLE_243 = 1 << 243;
uint256 internal constant _ROLE_244 = 1 << 244;
uint256 internal constant _ROLE_245 = 1 << 245;
uint256 internal constant _ROLE_246 = 1 << 246;
uint256 internal constant _ROLE_247 = 1 << 247;
uint256 internal constant _ROLE_248 = 1 << 248;
uint256 internal constant _ROLE_249 = 1 << 249;
uint256 internal constant _ROLE_250 = 1 << 250;
uint256 internal constant _ROLE_251 = 1 << 251;
uint256 internal constant _ROLE_252 = 1 << 252;
uint256 internal constant _ROLE_253 = 1 << 253;
uint256 internal constant _ROLE_254 = 1 << 254;
uint256 internal constant _ROLE_255 = 1 << 255;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
/// to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNATURE CHECKING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
if eq(mload(signature), 64) {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), mload(0x60)) // `s`.
mstore8(add(m, 0xa4), mload(0x20)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, s) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: These ERC1271 operations do NOT have an ECDSA fallback.
// These functions are intended to be used with the regular `isValidSignatureNow` functions
// or other signature verification functions (e.g. P256).
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC6492 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: These ERC6492 operations do NOT have an ECDSA fallback.
// These functions are intended to be used with the regular `isValidSignatureNow` functions
// or other signature verification functions (e.g. P256).
// The calldata variants are excluded for brevity.
/// @dev Returns whether `signature` is valid for `hash`.
/// If the signature is postfixed with the ERC6492 magic number, it will attempt to
/// deploy / prepare the `signer` smart account before doing a regular ERC1271 check.
/// Note: This function is NOT reentrancy safe.
function isValidERC6492SignatureNowAllowSideEffects(
address signer,
bytes32 hash,
bytes memory signature
) internal returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
let m_ := mload(0x40)
let f_ := shl(224, 0x1626ba7e)
mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m_, 0x04), hash_)
let d_ := add(m_, 0x24)
mstore(d_, 0x40) // The offset of the `signature` in the calldata.
let n_ := add(0x20, mload(signature_))
pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
_isValid :=
and(
eq(mload(d_), f_),
staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
)
}
for { let n := mload(signature) } 1 {} {
if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
isValid := callIsValidSignature(signer, hash, signature)
break
}
let o := add(signature, 0x20) // Signature bytes.
let d := add(o, mload(add(o, 0x20))) // Factory calldata.
if iszero(extcodesize(signer)) {
if iszero(call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00)) {
break
}
}
let s := add(o, mload(add(o, 0x40))) // Inner signature.
isValid := callIsValidSignature(signer, hash, s)
if iszero(isValid) {
if call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00) {
isValid := callIsValidSignature(signer, hash, s)
}
}
break
}
}
}
/// @dev Returns whether `signature` is valid for `hash`.
/// If the signature is postfixed with the ERC6492 magic number, it will attempt
/// to use a reverting verifier to deploy / prepare the `signer` smart account
/// and do a `isValidSignature` check via the reverting verifier.
/// Note: This function is reentrancy safe.
/// The reverting verifier must be be deployed.
/// Otherwise, the function will return false if `signer` is not yet deployed / prepared.
/// See: https://gist.github.com/Vectorized/846a474c855eee9e441506676800a9ad
function isValidERC6492SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
let m_ := mload(0x40)
let f_ := shl(224, 0x1626ba7e)
mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m_, 0x04), hash_)
let d_ := add(m_, 0x24)
mstore(d_, 0x40) // The offset of the `signature` in the calldata.
let n_ := add(0x20, mload(signature_))
pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
_isValid :=
and(
eq(mload(d_), f_),
staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
)
}
for { let n := mload(signature) } 1 {} {
if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
isValid := callIsValidSignature(signer, hash, signature)
break
}
if extcodesize(signer) {
let o := add(signature, 0x20) // Signature bytes.
isValid := callIsValidSignature(signer, hash, add(o, mload(add(o, 0x40))))
if isValid { break }
}
let m := mload(0x40)
mstore(m, signer)
mstore(add(m, 0x20), hash)
let willBeZeroIfRevertingVerifierExists :=
call(
gas(), // Remaining gas.
0x00007bd799e4A591FeA53f8A8a3E9f931626Ba7e, // Reverting verifier.
0, // Send zero ETH.
m, // Start of memory.
add(returndatasize(), 0x40), // Length of calldata in memory.
staticcall(gas(), 4, add(signature, 0x20), n, add(m, 0x40), n), // 1.
0x00 // Length of returndata to write.
)
isValid := gt(returndatasize(), willBeZeroIfRevertingVerifierExists)
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
interface IRebootTournament {
struct Entry {
uint256 ticketBasedCollateral;
uint256 creditBasedCollateral;
uint256 totalCollateralPaid;
uint248 rebuyCount;
bool payoutReceived;
}
struct Result {
address player;
uint256 multiplier;
}
struct EntryParams {
address account;
uint256 tournamentId;
uint256 roomId;
address swapFromCredit;
uint256 swapMinCollateral;
uint256 entryAmount;
}
struct Config {
uint256 entryFee; // credit fee to enter tournament (new entry for a new score)
uint256 rebuyFee; // credit fee to rebuy into tournament (re-attempt an existing entry to try to improve score)
bool isExactFee; // if true, above fees are exact required fees. if false, above fees are minimum fees
uint64 entryLimit; // max number of entries allowed per player
uint64 maxEntriesPerRoom; // max number of entries allowed per room
uint64 rebuyLimit; // max number of rebuys allowed per player per entry
uint64 startDate; // start date of tournament (block timestamp)
uint64 endDate; // end date of tournament (block timestamp)
uint64 ticketProfitToTickets; // % of profit from ticket entry converted to ticket payout (in wei)
uint64 creditProfitToTickets; // % of profit from credit entry converted to credit payout (in wei)
uint64 creditEntryToTickets; // % of credit entry converted to ticket payout (in wei)
uint64 entryDuration; // duration in seconds of entry period for a room once the room is opened
uint64 tournamentDuration; // duration in seconds of tournament instance (room) once started
uint64 payoutDuration; // duration in seconds to allow payouts for after tournament ends
uint96 creditRatio; // ratio of collateral to credits (gets set during register transaction)
address creditId; // credit id to use for entry fee (the id is the collateral token address)
uint256 bonusCollateral; // amount of collateral to request each time it's needed
bytes32 priceFeedPair; // pair to use for price feed (use bytes32(0) for a flat credit fee)
}
// Tournament IDs
function tournamentCount() external view returns (uint256);
// Allowed to set contract configs and register tournaments
function ADMIN_ROLE() external view returns (bytes32);
// Allowed to call `enter`, `rebuy` and `submitResults`
function RELAYER_ROLE() external view returns (bytes32);
/**
* @notice Get the tournament config set by contract admin
*
* @param _tournamentId ID of the tournament
*/
function getTournament(uint256 _tournamentId) external view returns (Config memory);
/**
* @notice Opens a new room for an active tournament
*
* @param _tournamentId ID of the tournament
* @param _roomId ID of the tournament room
* @param _openTimestamp Timestamp to open the room
*/
function createRoom(uint256 _tournamentId, uint256 _roomId, uint64 _openTimestamp) external;
/**
* @notice Enters a player into a tournament room. If the room does not exist, it will be created.
*
* @dev A player can only enter a specific room once.
*
* @param _params entry params struct
*/
function enter(EntryParams calldata _params) external payable;
/**
* @notice Allows a player to rebuy into a tournament room for a chance to improve their score
*
* @param _tournamentId ID of the tournament
* @param _account Address of player
* @param _roomId ID of the tournament room
* @param _swapFromCredit credit token to swap from, use zero address if swap not wanted
* @param _swapMinCollateral min amount of output collateral to receive from swap
* @param _entryAmount credit amount to spend to enter - if tournament config has `isExactFee` set to true, must be equivalent to `rebuyFee`. Otherwise, must be >= `rebuyFee`
*/
function rebuy(
uint256 _tournamentId,
address _account,
uint256 _roomId,
address _swapFromCredit,
uint256 _swapMinCollateral,
uint256 _entryAmount
) external payable;
/**
* @notice Submits the results of a tournament room. Payouts are calculated and credits are minted to players.
*
* @param _tournamentId ID of the tournament
* @param _roomId ID of the tournament room
* @param _results Array of results for the room
*/
function submitResults(uint256 _tournamentId, uint256 _roomId, Result[] memory _results) external;
function credits() external view returns (address);
/**
* @notice Admin function to register a new tournament
*
* @param _c Config struct for the tournament
*/
function register(Config memory _c) external returns (uint256 _tournamentId);
/**
* @notice Get the entry for a player in a room
*
* @param _tournamentId ID of the tournament
* @param _player Address of the player
* @param _roomId ID of the tournament room
*/
function getEntry(uint256 _tournamentId, address _player, uint256 _roomId) external view returns (Entry memory);
/**
* @notice Cleans up a tournament room after the payout duration has ended. Returns any remaining collateral to the game dev or bonus contract.
*
* @param _tournamentId ID of the tournament
* @param _roomId ID of the tournament room
*/
function cleanup(uint256 _tournamentId, uint256 _roomId) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
enum Direction {
LONG,
SHORT
}
struct Trade {
bytes32 pair;
uint40 lastUpdatedAt;
int104 entryPrice;
int104 amountInBips;
}
struct InternalPrice {
int64 price;
int32 expo;
uint40 publishTime;
}
struct TournamentConfig {
uint16 minimumHoldDuration;
uint16 maxLeverage;
uint40 startTime;
uint40 endTime;
uint128 startBalance;
mapping(bytes32 pair => InternalPrice finalPrice) finalPrices;
mapping(address user => int256 balance) userBalance;
mapping(address user => Trade trade) activeTrade;
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @title IPythEvents contains the events that Pyth contract emits.
/// @dev This interface can be used for listening to the updates for off-chain and testing purposes.
interface IPythEvents {
/// @dev Emitted when the price feed with `id` has received a fresh update.
/// @param id The Pyth Price Feed ID.
/// @param publishTime Publish time of the given price update.
/// @param price Price of the given price update.
/// @param conf Confidence interval of the given price update.
event PriceFeedUpdate(
bytes32 indexed id,
uint64 publishTime,
int64 price,
uint64 conf
);
/// @dev Emitted when a batch price update is processed successfully.
/// @param chainId ID of the source chain that the batch price update comes from.
/// @param sequenceNumber Sequence number of the batch price update.
event BatchPriceFeedUpdate(uint16 chainId, uint64 sequenceNumber);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}