Source Code
Latest 25 from a total of 1,656,016 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Play | 29495591 | 17 days ago | IN | 0.0005017 ETH | 0.00005865 | ||||
| Refund | 29315872 | 21 days ago | IN | 0 ETH | 0.00000004 | ||||
| Play | 29312881 | 21 days ago | IN | 0.0001017 ETH | 0.00000011 | ||||
| Refund | 28886661 | 31 days ago | IN | 0 ETH | 0.00000003 | ||||
| Play | 28872539 | 31 days ago | IN | 0.0001017 ETH | 0.00000005 | ||||
| Play | 28269678 | 45 days ago | IN | 0.0000017 ETH | 0.00007672 | ||||
| Refund | 28215446 | 46 days ago | IN | 0 ETH | 0 | ||||
| Play | 28208102 | 46 days ago | IN | 0.0001617 ETH | 0 | ||||
| Refund | 28160638 | 47 days ago | IN | 0 ETH | 0 | ||||
| Play | 28148504 | 48 days ago | IN | 0.0010017 ETH | 0 | ||||
| Refund | 28094451 | 49 days ago | IN | 0 ETH | 0 | ||||
| Play | 28094061 | 49 days ago | IN | 0.0000017 ETH | 0 | ||||
| Play | 28092402 | 49 days ago | IN | 0.0030017 ETH | 0 | ||||
| Refund | 28057453 | 50 days ago | IN | 0 ETH | 0 | ||||
| Refund | 28054554 | 50 days ago | IN | 0 ETH | 0 | ||||
| Play | 28054112 | 50 days ago | IN | 0.0010017 ETH | 0 | ||||
| Play | 28030769 | 50 days ago | IN | 0.0001617 ETH | 0 | ||||
| Refund | 28011527 | 51 days ago | IN | 0 ETH | 0 | ||||
| Play | 28007031 | 51 days ago | IN | 0.0010017 ETH | 0.00000657 | ||||
| Refund | 28006143 | 51 days ago | IN | 0 ETH | 0.00000012 | ||||
| Play | 27993320 | 51 days ago | IN | 0.0001617 ETH | 0 | ||||
| Refund | 27979444 | 52 days ago | IN | 0 ETH | 0 | ||||
| Refund | 27969550 | 52 days ago | IN | 0 ETH | 0 | ||||
| Refund | 27965552 | 52 days ago | IN | 0 ETH | 0 | ||||
| Play | 27963490 | 52 days ago | IN | 0.0030017 ETH | 0.00000002 |
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 29853684 | 8 days ago | 0.0004017 ETH | ||||
| 29853667 | 8 days ago | 0.0001017 ETH | ||||
| 29633315 | 13 days ago | 0.0001017 ETH | ||||
| 29315872 | 21 days ago | 0.0001017 ETH | ||||
| 28886661 | 31 days ago | 0.0001017 ETH | ||||
| 28215446 | 46 days ago | 0.0001617 ETH | ||||
| 28160638 | 47 days ago | 0.0010017 ETH | ||||
| 28094451 | 49 days ago | 0.0030017 ETH | ||||
| 28057453 | 50 days ago | 0.0010017 ETH | ||||
| 28054554 | 50 days ago | 0.0001617 ETH | ||||
| 28011527 | 51 days ago | 0.0010017 ETH | ||||
| 28006143 | 51 days ago | 0.0001617 ETH | ||||
| 27979444 | 52 days ago | 0.0000017 ETH | ||||
| 27969550 | 52 days ago | 0.0001617 ETH | ||||
| 27965552 | 52 days ago | 0.0030017 ETH | ||||
| 27950060 | 52 days ago | 0.0030017 ETH | ||||
| 27943339 | 52 days ago | 0.0001617 ETH | ||||
| 27929246 | 53 days ago | 0.0030017 ETH | ||||
| 27912138 | 53 days ago | 0.0010017 ETH | ||||
| 27902393 | 53 days ago | 0.0001617 ETH | ||||
| 27877597 | 54 days ago | 0.0003217 ETH | ||||
| 27874487 | 54 days ago | 0.0001617 ETH | ||||
| 27868759 | 54 days ago | 0.0000017 ETH | ||||
| 27868759 | 54 days ago | 0.00126 ETH | ||||
| 27868754 | 54 days ago | 0.0000017 ETH |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
Quantum
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 888888 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol";
import {Game} from "./Game.sol";
import {IGameConfigurationManager} from "./interfaces/IGameConfigurationManager.sol";
/**
* @title Quantum
* @notice Players can play quantum against a liquidity pool via this contract.
* @author YOLO Games protocol team
*/
contract Quantum is Game {
/**
* @dev The total possible outcomes of the quantum game
*/
uint256 private constant TOTAL_OUTCOMES = 10_000_000 * 1e18;
/**
* @notice The game struct
* @param params The game parameters
* @param isAbove The result must be above the selected number if this is true and vice versa
* @param multiplier Multiplier is used to determine the number that the result must be above or below.
* The higher the multiplier, the lower the probability to win.
*/
struct Quantum__Game {
Game__GameParams params;
bool isAbove;
uint248 multiplier;
}
mapping(address player => Quantum__Game) public games;
event Quantum__GameCreated(
uint256 blockNumber,
address player,
uint256 numberOfRounds,
uint256 playAmountPerRound,
address currency,
int256 stopGain,
int256 stopLoss,
bool isAbove,
uint256 multiplier
);
event Quantum__GameCompleted(
uint256 blockNumber,
address player,
uint256[] results,
uint256[] payouts,
uint256 numberOfRoundsPlayed,
uint256 protocolFee,
uint256 liquidityPoolFee
);
/**
* @param _gameConfigurationManager Liquidity manager address
* @param _transferManager Transfer manager address
* @param _weth WETH address
* @param _vrfCoordinator The address of the VRF coordinator for Chainlink VRF. It is set as our VRF coordinator adapter for Gelato.
* @param _blast Blast precompile
* @param _usdb USDB address
* @param _owner The owner of the contract
* @param _blastPoints Blast points
* @param _blastPointsOperator Blast points operator
*/
constructor(
address _gameConfigurationManager,
address _transferManager,
address _weth,
address _vrfCoordinator,
address _blast,
address _usdb,
address _owner,
address _blastPoints,
address _blastPointsOperator
)
Game(
_gameConfigurationManager,
_transferManager,
_weth,
_vrfCoordinator,
_blast,
_usdb,
_owner,
_blastPoints,
_blastPointsOperator
)
{}
/**
* @notice Play a new game
*
* @param numberOfRounds The number of rounds to play
* @param playAmountPerRound The amount to play per round
* @param currency The currency to play with
* @param stopGain The stop gain amount
* @param stopLoss The stop loss amount
* @param isAbove The result must be above the selected number if this is true and vice versa
* @param multiplier Multiplier is used to determine the number that the result must be above or below.
* The higher the multiplier, the lower the probability to win.
*
* @dev The minimum multiplier is 10,526 and the maximum multiplier is 10,000,000.
* This number is based on the maximum and minimum win probability of 95% and 0.1% with a house edge of 1% respectively.
* The multiplier formula is 100 / win probability.
* When the win probability is 95%, the multiplier = 100 / 95 = 1.0526.
* When the win probability is 0.1%, the multiplier = 100 / 0.1 = 1,000.
*
* All values are scaled by 1e4.
*/
function play(
uint16 numberOfRounds,
uint256 playAmountPerRound,
address currency,
int256 stopGain,
int256 stopLoss,
bool isAbove,
uint248 multiplier
) external payable nonReentrant {
_validateNumberOfRoundsAndPlayAmountPerRound(numberOfRounds, playAmountPerRound);
_validateNoOngoingRound(games[msg.sender].params.numberOfRounds);
_validateStopGainAndLoss(stopGain, stopLoss);
_validateMultiplier(multiplier);
uint256 totalPlayAmount = playAmountPerRound * numberOfRounds;
uint256 _maxPlayAmountPerGame = maxPlayAmountPerGame(currency, multiplier);
if (totalPlayAmount > _maxPlayAmountPerGame) {
revert Game__PlayAmountPerRoundTooHigh();
}
if (totalPlayAmount < _minPlayAmountPerGame(_maxPlayAmountPerGame, currency)) {
revert Game__PlayAmountPerRoundTooLow();
}
_validateLiquidityPoolIsNotPaused(currency);
(uint256 vrfFee, uint256 requestId) = _requestRandomness();
_escrowPlayAmountAndChargeVrfFee(currency, numberOfRounds, playAmountPerRound, vrfFee);
IGameConfigurationManager.FeeSplit memory feeSplit = GAME_CONFIGURATION_MANAGER.getFeeSplit(address(this));
games[msg.sender] = Quantum__Game({
params: Game__GameParams({
blockNumber: uint40(block.number),
numberOfRounds: numberOfRounds,
playAmountPerRound: playAmountPerRound,
currency: currency,
stopGain: stopGain,
stopLoss: stopLoss,
randomnessRequestedAt: uint40(block.timestamp),
vrfFee: vrfFee,
requestId: requestId,
protocolFeeBasisPoints: feeSplit.protocolFeeBasisPoints,
liquidityPoolFeeBasisPoints: feeSplit.liquidityPoolFeeBasisPoints
}),
isAbove: isAbove,
multiplier: multiplier
});
emit Quantum__GameCreated(
block.number,
msg.sender,
numberOfRounds,
playAmountPerRound,
currency,
stopGain,
stopLoss,
isAbove,
multiplier
);
}
/**
* @notice Refund the player if the game is not completed after a certain time
*/
function refund() external nonReentrant {
_refund(games[msg.sender].params);
_deleteGame(msg.sender);
}
/**
* @notice Return the maximum play amount per game for the given currency based on
* 1) The liquidity pool balance
* 2) The Kelly Criterion
* 3) The Kelly Fraction basis points defined by the game configuration manager
*
* @param currency The currency to play with
* @param multiplier The selected multiplier
*
* @return maxPlayAmount The maximum play amount per game
*/
function maxPlayAmountPerGame(address currency, uint256 multiplier) public view returns (uint256 maxPlayAmount) {
_validateMultiplier(multiplier);
maxPlayAmount =
(_liquidityPoolBalance(currency) *
kellyFraction(multiplier) *
GAME_CONFIGURATION_MANAGER.kellyFractionBasisPoints(address(this))) /
1e18 /
10_000;
}
/**
* @notice The minimum play amount per game is the lesser between 0.01% of the maximum play amount per game and owner set minimum play amount.
*
* @param currency The currency to play with
* @param multiplier The selected multiplier
*/
function minPlayAmountPerGame(address currency, uint256 multiplier) public view returns (uint256 minPlayAmount) {
minPlayAmount = _minPlayAmountPerGame(maxPlayAmountPerGame(currency, multiplier), currency);
}
/**
* @notice Returns the Kelly Fraction for the selected multiplier, scaled by 1e10
*
* @dev The Kelly Fraction of the liquidity pool that should be used
* as the counterparty to the user can be represented as:
* x = (p / a) - (1 - p) / b
* where:
* x is the optimal fraction of the liquidity pool that should be used as a counterparty to the user
* p is the probability of YOLO Games winning the round
* a is the proportion of the amount lost on a loss
* b is the proportion of the amount won on a win
*
* The probability depends on the multiplier.
* Let's assume we take a 2% fee on loss (1% to treasury, 1% to LP, configurable)
* On a loss (player wins), we would usually lose the entire amount, however as we take a 2% fee for our losses, we only lose 98%.
* On a win (player loses), we win the entire amount.
*
* If the multiplier is 10,526, the Kelly Criterion is
* (10,000,000 - 9,500,285) / (10,526,000 * 0.99 - 10,000,000) - 9,500,258 / 10,000,000 = 0.237679196
*
* If the multiplier is 10,000,000, the Kelly Criterion is
* (10,000,000 - 10,000) / (10,000,000,000 * 0.99 - 10,000,000) - 10,000 / 10,000,000 = 0.000010111
*
* @param multiplier The selected multiplier
* @return kellyFraction The Kelly Fraction given the selected multiplier
*/
function kellyFraction(uint256 multiplier) public view returns (uint256) {
_validateMultiplier(multiplier);
uint256 winProbability = calculateWinProbability(multiplier);
return
FixedPointMathLib.divWad(
(TOTAL_OUTCOMES - winProbability),
FixedPointMathLib.divWad(multiplier * _liquidityProviderAdjustedReturn() * 1_000, 10_000) -
TOTAL_OUTCOMES
) - FixedPointMathLib.divWad(winProbability, TOTAL_OUTCOMES);
}
/**
* @param requestId The ID of the request
* @param randomWords The random words returned by Chainlink
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override nonReentrant {
address player = randomnessRequests[requestId];
if (player != address(0)) {
Quantum__Game storage game = games[player];
if (
_hasLiquidityPool(game.params.currency) &&
_vrfResponseIsNotTooLate(game.params.randomnessRequestedAt) &&
requestId == game.params.requestId
) {
randomnessRequests[requestId] = address(0);
RunningGameState memory runningGameState;
runningGameState.payouts = new uint256[](game.params.numberOfRounds);
runningGameState.randomWord = randomWords[0];
uint256[] memory results = new uint256[](game.params.numberOfRounds);
Fee memory fee;
for (
;
runningGameState.numberOfRoundsPlayed < game.params.numberOfRounds;
++runningGameState.numberOfRoundsPlayed
) {
if (
_stopGainOrStopLossHit(game.params.stopGain, game.params.stopLoss, runningGameState.netAmount)
) {
break;
}
results[runningGameState.numberOfRoundsPlayed] = runningGameState.randomWord % TOTAL_OUTCOMES;
if (
(game.isAbove &&
results[runningGameState.numberOfRoundsPlayed] >=
defineBoundary(calculateWinProbability(game.multiplier))) ||
(!game.isAbove &&
results[runningGameState.numberOfRoundsPlayed] < calculateWinProbability(game.multiplier))
) {
uint256 protocolFee = (game.multiplier *
game.params.playAmountPerRound *
game.params.protocolFeeBasisPoints) / 1e8;
uint256 liquidityPoolFee = (game.multiplier *
game.params.playAmountPerRound *
game.params.liquidityPoolFeeBasisPoints) / 1e8;
runningGameState.payouts[runningGameState.numberOfRoundsPlayed] =
((game.multiplier * game.params.playAmountPerRound) / 10_000) -
protocolFee -
liquidityPoolFee;
runningGameState.payout += runningGameState.payouts[runningGameState.numberOfRoundsPlayed];
runningGameState.netAmount += (int256(
runningGameState.payouts[runningGameState.numberOfRoundsPlayed]
) - int256(game.params.playAmountPerRound));
fee.protocolFee += protocolFee;
fee.liquidityPoolFee += liquidityPoolFee;
} else {
runningGameState.netAmount -= int256(game.params.playAmountPerRound);
}
runningGameState.randomWord = uint256(keccak256(abi.encode(runningGameState.randomWord)));
}
_handlePayout(
player,
game.params,
runningGameState.numberOfRoundsPlayed,
runningGameState.payout,
fee.protocolFee
);
_transferVrfFee(game.params.vrfFee);
emit Quantum__GameCompleted(
game.params.blockNumber,
player,
results,
runningGameState.payouts,
runningGameState.numberOfRoundsPlayed,
fee.protocolFee,
fee.liquidityPoolFee
);
_deleteGame(player);
}
}
}
/**
* @notice Calculate the win probability based on the provided multiplier
* @param multiplier The multiplier is used to determine the number that the result must be above or below
*/
function calculateWinProbability(uint256 multiplier) public pure returns (uint256 winProbability) {
winProbability = FixedPointMathLib.divWad(100_000_000_000, multiplier);
}
/**
* @notice Define the boundary based on the provided win probability
* @param winProbability The win probability
*/
function defineBoundary(uint256 winProbability) public pure returns (uint256 boundary) {
if (winProbability > TOTAL_OUTCOMES) {
revert Game__InvalidValue();
}
boundary = TOTAL_OUTCOMES - winProbability;
}
/**
* @param player The player address
*/
function _deleteGame(address player) private {
games[player] = Quantum__Game({
params: Game__GameParams({
blockNumber: 0,
numberOfRounds: 0,
playAmountPerRound: 0,
currency: address(0),
stopGain: 0,
stopLoss: 0,
randomnessRequestedAt: 0,
vrfFee: 0,
requestId: 0,
protocolFeeBasisPoints: 0,
liquidityPoolFeeBasisPoints: 0
}),
isAbove: false,
multiplier: 0
});
}
/**
* @param multiplier The selected multiplier
*/
function _validateMultiplier(uint256 multiplier) private pure {
if (multiplier < 10_526 || multiplier > 10_000_000) {
revert Game__InvalidMultiplier();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface VRFCoordinatorV2Interface {
/**
* @notice Get configuration relevant for making requests
* @return minimumRequestConfirmations global min for request confirmations
* @return maxGasLimit global max for request gas limit
* @return s_provingKeyHashes list of registered key hashes
*/
function getRequestConfig()
external
view
returns (
uint16,
uint32,
bytes32[] memory
);
/**
* @notice Request a set of random words.
* @param keyHash - Corresponds to a particular oracle job which uses
* that key for generating the VRF proof. Different keyHash's have different gas price
* ceilings, so you can select a specific one to bound your maximum per request cost.
* @param subId - The ID of the VRF subscription. Must be funded
* with the minimum subscription balance required for the selected keyHash.
* @param minimumRequestConfirmations - How many blocks you'd like the
* oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
* for why you may want to request more. The acceptable range is
* [minimumRequestBlockConfirmations, 200].
* @param callbackGasLimit - How much gas you'd like to receive in your
* fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
* may be slightly less than this amount because of gas used calling the function
* (argument decoding etc.), so you may need to request slightly more than you expect
* to have inside fulfillRandomWords. The acceptable range is
* [0, maxGasLimit]
* @param numWords - The number of uint256 random values you'd like to receive
* in your fulfillRandomWords callback. Note these numbers are expanded in a
* secure way by the VRFCoordinator from a single random value supplied by the oracle.
* @return requestId - A unique identifier of the request. Can be used to match
* a request to a response in fulfillRandomWords.
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256 requestId);
/**
* @notice Create a VRF subscription.
* @return subId - A unique subscription id.
* @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
* @dev Note to fund the subscription, use transferAndCall. For example
* @dev LINKTOKEN.transferAndCall(
* @dev address(COORDINATOR),
* @dev amount,
* @dev abi.encode(subId));
*/
function createSubscription() external returns (uint64 subId);
/**
* @notice Get a VRF subscription.
* @param subId - ID of the subscription
* @return balance - LINK balance of the subscription in juels.
* @return reqCount - number of requests for this subscription, determines fee tier.
* @return owner - owner of the subscription.
* @return consumers - list of consumer address which are able to use this subscription.
*/
function getSubscription(uint64 subId)
external
view
returns (
uint96 balance,
uint64 reqCount,
address owner,
address[] memory consumers
);
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @param newOwner - proposed new owner of the subscription
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @dev will revert if original owner of subId has
* not requested that msg.sender become the new owner.
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external;
/**
* @notice Add a consumer to a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - New consumer which can use the subscription
*/
function addConsumer(uint64 subId, address consumer) external;
/**
* @notice Remove a consumer from a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - Consumer to remove from the subscription
*/
function removeConsumer(uint64 subId, address consumer) external;
/**
* @notice Cancel a subscription
* @param subId - ID of the subscription
* @param to - Where to send the remaining LINK to
*/
function cancelSubscription(uint64 subId, address to) external;
/*
* @notice Check to see if there exists a request commitment consumers
* for all consumers and keyhashes for a given sub.
* @param subId - ID of the subscription
* @return true if there exists at least one unfulfilled request for the subscription, false
* otherwise.
*/
function pendingRequestExists(uint64 subId) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/** ****************************************************************************
* @notice Interface for contracts using VRF randomness
* *****************************************************************************
* @dev PURPOSE
*
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
*
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is indistinguishable to her from a uniform random sample
* @dev from the output space.
*
* @dev The purpose of this contract is to make it easy for unrelated contracts
* @dev to talk to Vera the verifier about the work Reggie is doing, to provide
* @dev simple access to a verifiable source of randomness. It ensures 2 things:
* @dev 1. The fulfillment came from the VRFCoordinator
* @dev 2. The consumer contract implements fulfillRandomWords.
* *****************************************************************************
* @dev USAGE
*
* @dev Calling contracts must inherit from VRFConsumerBase, and can
* @dev initialize VRFConsumerBase's attributes in their constructor as
* @dev shown:
*
* @dev contract VRFConsumer {
* @dev constructor(<other arguments>, address _vrfCoordinator, address _link)
* @dev VRFConsumerBase(_vrfCoordinator) public {
* @dev <initialization with other arguments goes here>
* @dev }
* @dev }
*
* @dev The oracle will have given you an ID for the VRF keypair they have
* @dev committed to (let's call it keyHash). Create subscription, fund it
* @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
* @dev subscription management functions).
* @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
* @dev callbackGasLimit, numWords),
* @dev see (VRFCoordinatorInterface for a description of the arguments).
*
* @dev Once the VRFCoordinator has received and validated the oracle's response
* @dev to your request, it will call your contract's fulfillRandomWords method.
*
* @dev The randomness argument to fulfillRandomWords is a set of random words
* @dev generated from your requestId and the blockHash of the request.
*
* @dev If your contract could have concurrent requests open, you can use the
* @dev requestId returned from requestRandomWords to track which response is associated
* @dev with which randomness request.
* @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
* @dev if your contract could have multiple requests in flight simultaneously.
*
* @dev Colliding `requestId`s are cryptographically impossible as long as seeds
* @dev differ.
*
* *****************************************************************************
* @dev SECURITY CONSIDERATIONS
*
* @dev A method with the ability to call your fulfillRandomness method directly
* @dev could spoof a VRF response with any random value, so it's critical that
* @dev it cannot be directly called by anything other than this base contract
* @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
*
* @dev For your users to trust that your contract's random behavior is free
* @dev from malicious interference, it's best if you can write it so that all
* @dev behaviors implied by a VRF response are executed *during* your
* @dev fulfillRandomness method. If your contract must store the response (or
* @dev anything derived from it) and use it later, you must ensure that any
* @dev user-significant behavior which depends on that stored value cannot be
* @dev manipulated by a subsequent VRF request.
*
* @dev Similarly, both miners and the VRF oracle itself have some influence
* @dev over the order in which VRF responses appear on the blockchain, so if
* @dev your contract could have multiple VRF requests in flight simultaneously,
* @dev you must ensure that the order in which the VRF responses arrive cannot
* @dev be used to manipulate your contract's user-significant behavior.
*
* @dev Since the block hash of the block which contains the requestRandomness
* @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
* @dev miner could, in principle, fork the blockchain to evict the block
* @dev containing the request, forcing the request to be included in a
* @dev different block with a different hash, and therefore a different input
* @dev to the VRF. However, such an attack would incur a substantial economic
* @dev cost. This cost scales with the number of blocks the VRF oracle waits
* @dev until it calls responds to a request. It is for this reason that
* @dev that you can signal to an oracle you'd like them to wait longer before
* @dev responding to the request (however this is not enforced in the contract
* @dev and so remains effective only in the case of unmodified oracle software).
*/
abstract contract VRFConsumerBaseV2 {
error OnlyCoordinatorCanFulfill(address have, address want);
address private immutable vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) {
vrfCoordinator = _vrfCoordinator;
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
if (msg.sender != vrfCoordinator) {
revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
}
fulfillRandomWords(requestId, randomWords);
}
}// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** * @notice It is emitted if the call recipient is not a contract. */ error NotAContract();
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; /** * @notice It is emitted if the ETH transfer fails. */ error ETHTransferFail(); /** * @notice It is emitted if the ERC20 approval fails. */ error ERC20ApprovalFail(); /** * @notice It is emitted if the ERC20 transfer fails. */ error ERC20TransferFail(); /** * @notice It is emitted if the ERC20 transferFrom fails. */ error ERC20TransferFromFail(); /** * @notice It is emitted if the ERC721 transferFrom fails. */ error ERC721TransferFromFail(); /** * @notice It is emitted if the ERC1155 safeTransferFrom fails. */ error ERC1155SafeTransferFromFail(); /** * @notice It is emitted if the ERC1155 safeBatchTransferFrom fails. */ error ERC1155SafeBatchTransferFromFail();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
interface IWETH {
function deposit() external payable;
function transfer(address dst, uint256 wad) external returns (bool);
function withdraw(uint256 wad) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @title IOwnableTwoSteps
* @author LooksRare protocol team (👀,💎)
*/
interface IOwnableTwoSteps {
/**
* @notice This enum keeps track of the ownership status.
* @param NoOngoingTransfer The default status when the owner is set
* @param TransferInProgress The status when a transfer to a new owner is initialized
* @param RenouncementInProgress The status when a transfer to address(0) is initialized
*/
enum Status {
NoOngoingTransfer,
TransferInProgress,
RenouncementInProgress
}
/**
* @notice This is returned when there is no transfer of ownership in progress.
*/
error NoOngoingTransferInProgress();
/**
* @notice This is returned when the caller is not the owner.
*/
error NotOwner();
/**
* @notice This is returned when there is no renouncement in progress but
* the owner tries to validate the ownership renouncement.
*/
error RenouncementNotInProgress();
/**
* @notice This is returned when the transfer is already in progress but the owner tries
* initiate a new ownership transfer.
*/
error TransferAlreadyInProgress();
/**
* @notice This is returned when there is no ownership transfer in progress but the
* ownership change tries to be approved.
*/
error TransferNotInProgress();
/**
* @notice This is returned when the ownership transfer is attempted to be validated by the
* a caller that is not the potential owner.
*/
error WrongPotentialOwner();
/**
* @notice This is emitted if the ownership transfer is cancelled.
*/
event CancelOwnershipTransfer();
/**
* @notice This is emitted if the ownership renouncement is initiated.
*/
event InitiateOwnershipRenouncement();
/**
* @notice This is emitted if the ownership transfer is initiated.
* @param previousOwner Previous/current owner
* @param potentialOwner Potential/future owner
*/
event InitiateOwnershipTransfer(address previousOwner, address potentialOwner);
/**
* @notice This is emitted when there is a new owner.
*/
event NewOwner(address newOwner);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @title IReentrancyGuard
* @author LooksRare protocol team (👀,💎)
*/
interface IReentrancyGuard {
/**
* @notice This is returned when there is a reentrant call.
*/
error ReentrancyFail();
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Interfaces
import {IERC20} from "../interfaces/generic/IERC20.sol";
// Errors
import {ERC20TransferFail, ERC20TransferFromFail} from "../errors/LowLevelErrors.sol";
import {NotAContract} from "../errors/GenericErrors.sol";
/**
* @title LowLevelERC20Transfer
* @notice This contract contains low-level calls to transfer ERC20 tokens.
* @author LooksRare protocol team (👀,💎)
*/
contract LowLevelERC20Transfer {
/**
* @notice Execute ERC20 transferFrom
* @param currency Currency address
* @param from Sender address
* @param to Recipient address
* @param amount Amount to transfer
*/
function _executeERC20TransferFrom(address currency, address from, address to, uint256 amount) internal {
if (currency.code.length == 0) {
revert NotAContract();
}
(bool status, bytes memory data) = currency.call(abi.encodeCall(IERC20.transferFrom, (from, to, amount)));
if (!status) {
revert ERC20TransferFromFail();
}
if (data.length > 0) {
if (!abi.decode(data, (bool))) {
revert ERC20TransferFromFail();
}
}
}
/**
* @notice Execute ERC20 (direct) transfer
* @param currency Currency address
* @param to Recipient address
* @param amount Amount to transfer
*/
function _executeERC20DirectTransfer(address currency, address to, uint256 amount) internal {
if (currency.code.length == 0) {
revert NotAContract();
}
(bool status, bytes memory data) = currency.call(abi.encodeCall(IERC20.transfer, (to, amount)));
if (!status) {
revert ERC20TransferFail();
}
if (data.length > 0) {
if (!abi.decode(data, (bool))) {
revert ERC20TransferFail();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Interfaces
import {IWETH} from "../interfaces/generic/IWETH.sol";
/**
* @title LowLevelWETH
* @notice This contract contains a function to transfer ETH with an option to wrap to WETH.
* If the ETH transfer fails within a gas limit, the amount in ETH is wrapped to WETH and then transferred.
* @author LooksRare protocol team (👀,💎)
*/
contract LowLevelWETH {
/**
* @notice It transfers ETH to a recipient with a specified gas limit.
* If the original transfers fails, it wraps to WETH and transfers the WETH to recipient.
* @param _WETH WETH address
* @param _to Recipient address
* @param _amount Amount to transfer
* @param _gasLimit Gas limit to perform the ETH transfer
*/
function _transferETHAndWrapIfFailWithGasLimit(
address _WETH,
address _to,
uint256 _amount,
uint256 _gasLimit
) internal {
bool status;
assembly {
status := call(_gasLimit, _to, _amount, 0, 0, 0, 0)
}
if (!status) {
IWETH(_WETH).deposit{value: _amount}();
IWETH(_WETH).transfer(_to, _amount);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Interfaces
import {IOwnableTwoSteps} from "./interfaces/IOwnableTwoSteps.sol";
/**
* @title OwnableTwoSteps
* @notice This contract offers transfer of ownership in two steps with potential owner
* having to confirm the transaction to become the owner.
* Renouncement of the ownership is also a two-step process since the next potential owner is the address(0).
* @author LooksRare protocol team (👀,💎)
*/
abstract contract OwnableTwoSteps is IOwnableTwoSteps {
/**
* @notice Address of the current owner.
*/
address public owner;
/**
* @notice Address of the potential owner.
*/
address public potentialOwner;
/**
* @notice Ownership status.
*/
Status public ownershipStatus;
/**
* @notice Modifier to wrap functions for contracts that inherit this contract.
*/
modifier onlyOwner() {
_onlyOwner();
_;
}
/**
* @notice Constructor
* @param _owner The contract's owner
*/
constructor(address _owner) {
owner = _owner;
emit NewOwner(_owner);
}
/**
* @notice This function is used to cancel the ownership transfer.
* @dev This function can be used for both cancelling a transfer to a new owner and
* cancelling the renouncement of the ownership.
*/
function cancelOwnershipTransfer() external onlyOwner {
Status _ownershipStatus = ownershipStatus;
if (_ownershipStatus == Status.NoOngoingTransfer) {
revert NoOngoingTransferInProgress();
}
if (_ownershipStatus == Status.TransferInProgress) {
delete potentialOwner;
}
delete ownershipStatus;
emit CancelOwnershipTransfer();
}
/**
* @notice This function is used to confirm the ownership renouncement.
*/
function confirmOwnershipRenouncement() external onlyOwner {
if (ownershipStatus != Status.RenouncementInProgress) {
revert RenouncementNotInProgress();
}
delete owner;
delete ownershipStatus;
emit NewOwner(address(0));
}
/**
* @notice This function is used to confirm the ownership transfer.
* @dev This function can only be called by the current potential owner.
*/
function confirmOwnershipTransfer() external {
if (ownershipStatus != Status.TransferInProgress) {
revert TransferNotInProgress();
}
if (msg.sender != potentialOwner) {
revert WrongPotentialOwner();
}
owner = msg.sender;
delete ownershipStatus;
delete potentialOwner;
emit NewOwner(msg.sender);
}
/**
* @notice This function is used to initiate the transfer of ownership to a new owner.
* @param newPotentialOwner New potential owner address
*/
function initiateOwnershipTransfer(address newPotentialOwner) external onlyOwner {
if (ownershipStatus != Status.NoOngoingTransfer) {
revert TransferAlreadyInProgress();
}
ownershipStatus = Status.TransferInProgress;
potentialOwner = newPotentialOwner;
/**
* @dev This function can only be called by the owner, so msg.sender is the owner.
* We don't have to SLOAD the owner again.
*/
emit InitiateOwnershipTransfer(msg.sender, newPotentialOwner);
}
/**
* @notice This function is used to initiate the ownership renouncement.
*/
function initiateOwnershipRenouncement() external onlyOwner {
if (ownershipStatus != Status.NoOngoingTransfer) {
revert TransferAlreadyInProgress();
}
ownershipStatus = Status.RenouncementInProgress;
emit InitiateOwnershipRenouncement();
}
function _onlyOwner() private view {
if (msg.sender != owner) revert NotOwner();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @title Pausable
* @notice This contract makes it possible to pause the contract.
* It is adjusted from OpenZeppelin.
* @author LooksRare protocol team (👀,💎)
*/
abstract contract Pausable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
error IsPaused();
error NotPaused();
bool private _paused;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert IsPaused();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert NotPaused();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(msg.sender);
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(msg.sender);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Interfaces
import {IReentrancyGuard} from "./interfaces/IReentrancyGuard.sol";
/**
* @title ReentrancyGuard
* @notice This contract protects against reentrancy attacks.
* It is adjusted from OpenZeppelin.
* @author LooksRare protocol team (👀,💎)
*/
abstract contract ReentrancyGuard is IReentrancyGuard {
uint256 private _status;
/**
* @notice Modifier to wrap functions to prevent reentrancy calls.
*/
modifier nonReentrant() {
if (_status == 2) {
revert ReentrancyFail();
}
_status = 2;
_;
_status = 1;
}
constructor() {
_status = 1;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
enum TokenType {
ERC20,
ERC721,
ERC1155
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Enums
import {TokenType} from "../enums/TokenType.sol";
/**
* @title ITransferManager
* @author LooksRare protocol team (👀,💎)
*/
interface ITransferManager {
/**
* @notice This struct is only used for transferBatchItemsAcrossCollections.
* @param tokenAddress Token address
* @param tokenType 0 for ERC721, 1 for ERC1155
* @param itemIds Array of item ids to transfer
* @param amounts Array of amounts to transfer
*/
struct BatchTransferItem {
address tokenAddress;
TokenType tokenType;
uint256[] itemIds;
uint256[] amounts;
}
/**
* @notice It is emitted if operators' approvals to transfer NFTs are granted by a user.
* @param user Address of the user
* @param operators Array of operator addresses
*/
event ApprovalsGranted(address user, address[] operators);
/**
* @notice It is emitted if operators' approvals to transfer NFTs are revoked by a user.
* @param user Address of the user
* @param operators Array of operator addresses
*/
event ApprovalsRemoved(address user, address[] operators);
/**
* @notice It is emitted if a new operator is added to the global allowlist.
* @param operator Operator address
*/
event OperatorAllowed(address operator);
/**
* @notice It is emitted if an operator is removed from the global allowlist.
* @param operator Operator address
*/
event OperatorRemoved(address operator);
/**
* @notice It is returned if the operator to approve has already been approved by the user.
*/
error OperatorAlreadyApprovedByUser();
/**
* @notice It is returned if the operator to revoke has not been previously approved by the user.
*/
error OperatorNotApprovedByUser();
/**
* @notice It is returned if the transfer caller is already allowed by the owner.
* @dev This error can only be returned for owner operations.
*/
error OperatorAlreadyAllowed();
/**
* @notice It is returned if the operator to approve is not in the global allowlist defined by the owner.
* @dev This error can be returned if the user tries to grant approval to an operator address not in the
* allowlist or if the owner tries to remove the operator from the global allowlist.
*/
error OperatorNotAllowed();
/**
* @notice It is returned if the transfer caller is invalid.
* For a transfer called to be valid, the operator must be in the global allowlist and
* approved by the 'from' user.
*/
error TransferCallerInvalid();
/**
* @notice This function transfers ERC20 tokens.
* @param tokenAddress Token address
* @param from Sender address
* @param to Recipient address
* @param amount amount
*/
function transferERC20(
address tokenAddress,
address from,
address to,
uint256 amount
) external;
/**
* @notice This function transfers a single item for a single ERC721 collection.
* @param tokenAddress Token address
* @param from Sender address
* @param to Recipient address
* @param itemId Item ID
*/
function transferItemERC721(
address tokenAddress,
address from,
address to,
uint256 itemId
) external;
/**
* @notice This function transfers items for a single ERC721 collection.
* @param tokenAddress Token address
* @param from Sender address
* @param to Recipient address
* @param itemIds Array of itemIds
* @param amounts Array of amounts
*/
function transferItemsERC721(
address tokenAddress,
address from,
address to,
uint256[] calldata itemIds,
uint256[] calldata amounts
) external;
/**
* @notice This function transfers a single item for a single ERC1155 collection.
* @param tokenAddress Token address
* @param from Sender address
* @param to Recipient address
* @param itemId Item ID
* @param amount Amount
*/
function transferItemERC1155(
address tokenAddress,
address from,
address to,
uint256 itemId,
uint256 amount
) external;
/**
* @notice This function transfers items for a single ERC1155 collection.
* @param tokenAddress Token address
* @param from Sender address
* @param to Recipient address
* @param itemIds Array of itemIds
* @param amounts Array of amounts
* @dev It does not allow batch transferring if from = msg.sender since native function should be used.
*/
function transferItemsERC1155(
address tokenAddress,
address from,
address to,
uint256[] calldata itemIds,
uint256[] calldata amounts
) external;
/**
* @notice This function transfers items across an array of tokens that can be ERC20, ERC721 and ERC1155.
* @param items Array of BatchTransferItem
* @param from Sender address
* @param to Recipient address
*/
function transferBatchItemsAcrossCollections(
BatchTransferItem[] calldata items,
address from,
address to
) external;
/**
* @notice This function allows a user to grant approvals for an array of operators.
* Users cannot grant approvals if the operator is not allowed by this contract's owner.
* @param operators Array of operator addresses
* @dev Each operator address must be globally allowed to be approved.
*/
function grantApprovals(address[] calldata operators) external;
/**
* @notice This function allows a user to revoke existing approvals for an array of operators.
* @param operators Array of operator addresses
* @dev Each operator address must be approved at the user level to be revoked.
*/
function revokeApprovals(address[] calldata operators) external;
/**
* @notice This function allows an operator to be added for the shared transfer system.
* Once the operator is allowed, users can grant NFT approvals to this operator.
* @param operator Operator address to allow
* @dev Only callable by owner.
*/
function allowOperator(address operator) external;
/**
* @notice This function allows the user to remove an operator for the shared transfer system.
* @param operator Operator address to remove
* @dev Only callable by owner.
*/
function removeOperator(address operator) external;
/**
* @notice This returns whether the user has approved the operator address.
* The first address is the user and the second address is the operator.
*/
function hasUserApprovedOperator(address user, address operator) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol";
import {SafeERC20} from "../utils/SafeERC20.sol";
import {IERC4626} from "../../../interfaces/IERC4626.sol";
import {Math} from "../../../utils/math/Math.sol";
/**
* @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in
* https://eips.ethereum.org/EIPS/eip-4626[EIP-4626].
*
* This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for
* underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
* the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this
* contract and not the "assets" token which is an independent contract.
*
* [CAUTION]
* ====
* In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
* with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
* attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
* deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
* similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
* verifying the amount received is as expected, using a wrapper that performs these checks such as
* https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
*
* Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()`
* corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault
* decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself
* determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset
* (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's
* donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more
* expensive than it is profitable. More details about the underlying math can be found
* xref:erc4626.adoc#inflation-attack[here].
*
* The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
* to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
* will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
* bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
* `_convertToShares` and `_convertToAssets` functions.
*
* To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
* ====
*/
abstract contract ERC4626 is ERC20, IERC4626 {
using Math for uint256;
IERC20 private immutable _asset;
uint8 private immutable _underlyingDecimals;
/**
* @dev Attempted to deposit more assets than the max amount for `receiver`.
*/
error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);
/**
* @dev Attempted to mint more shares than the max amount for `receiver`.
*/
error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);
/**
* @dev Attempted to withdraw more assets than the max amount for `receiver`.
*/
error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);
/**
* @dev Attempted to redeem more shares than the max amount for `receiver`.
*/
error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);
/**
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
*/
constructor(IERC20 asset_) {
(bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
_underlyingDecimals = success ? assetDecimals : 18;
_asset = asset_;
}
/**
* @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
*/
function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) {
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
abi.encodeCall(IERC20Metadata.decimals, ())
);
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
return (true, uint8(returnedDecimals));
}
}
return (false, 0);
}
/**
* @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
* "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
* asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
*
* See {IERC20Metadata-decimals}.
*/
function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
return _underlyingDecimals + _decimalsOffset();
}
/** @dev See {IERC4626-asset}. */
function asset() public view virtual returns (address) {
return address(_asset);
}
/** @dev See {IERC4626-totalAssets}. */
function totalAssets() public view virtual returns (uint256) {
return _asset.balanceOf(address(this));
}
/** @dev See {IERC4626-convertToShares}. */
function convertToShares(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets, Math.Rounding.Floor);
}
/** @dev See {IERC4626-convertToAssets}. */
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Floor);
}
/** @dev See {IERC4626-maxDeposit}. */
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
/** @dev See {IERC4626-maxMint}. */
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
/** @dev See {IERC4626-maxWithdraw}. */
function maxWithdraw(address owner) public view virtual returns (uint256) {
return _convertToAssets(balanceOf(owner), Math.Rounding.Floor);
}
/** @dev See {IERC4626-maxRedeem}. */
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf(owner);
}
/** @dev See {IERC4626-previewDeposit}. */
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets, Math.Rounding.Floor);
}
/** @dev See {IERC4626-previewMint}. */
function previewMint(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Ceil);
}
/** @dev See {IERC4626-previewWithdraw}. */
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets, Math.Rounding.Ceil);
}
/** @dev See {IERC4626-previewRedeem}. */
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Floor);
}
/** @dev See {IERC4626-deposit}. */
function deposit(uint256 assets, address receiver) public virtual returns (uint256) {
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) {
revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
}
uint256 shares = previewDeposit(assets);
_deposit(_msgSender(), receiver, assets, shares);
return shares;
}
/** @dev See {IERC4626-mint}.
*
* As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero.
* In this case, the shares will be minted without requiring any assets to be deposited.
*/
function mint(uint256 shares, address receiver) public virtual returns (uint256) {
uint256 maxShares = maxMint(receiver);
if (shares > maxShares) {
revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
}
uint256 assets = previewMint(shares);
_deposit(_msgSender(), receiver, assets, shares);
return assets;
}
/** @dev See {IERC4626-withdraw}. */
function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) {
uint256 maxAssets = maxWithdraw(owner);
if (assets > maxAssets) {
revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
}
uint256 shares = previewWithdraw(assets);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return shares;
}
/** @dev See {IERC4626-redeem}. */
function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) {
uint256 maxShares = maxRedeem(owner);
if (shares > maxShares) {
revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
}
uint256 assets = previewRedeem(shares);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return assets;
}
/**
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
*/
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
}
/**
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
*/
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
}
/**
* @dev Deposit/mint common workflow.
*/
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
// If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
// `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
// calls the vault, which is assumed not malicious.
//
// Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
// assets are transferred and before the shares are minted, which is a valid state.
// slither-disable-next-line reentrancy-no-eth
SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}
/**
* @dev Withdraw/redeem common workflow.
*/
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
// `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
// calls the vault, which is assumed not malicious.
//
// Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
// shares are burned and after the assets are transferred, which is a valid state.
_burn(owner, shares);
SafeERC20.safeTransfer(_asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
function _decimalsOffset() internal view virtual returns (uint8) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {OwnableTwoSteps} from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol";
import {LowLevelWETH} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelWETH.sol";
import {LowLevelERC20Transfer} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol";
import {ITransferManager} from "@looksrare/contracts-transfer-manager/contracts/interfaces/ITransferManager.sol";
import {Pausable} from "@looksrare/contracts-libs/contracts/Pausable.sol";
import {ReentrancyGuard} from "@looksrare/contracts-libs/contracts/ReentrancyGuard.sol";
import {VRFCoordinatorV2Interface} from "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {LiquidityPoolRouter} from "./LiquidityPoolRouter.sol";
import {IGameConfigurationManager} from "./interfaces/IGameConfigurationManager.sol";
import {ILiquidityPool} from "./interfaces/ILiquidityPool.sol";
import {IBlast, YieldMode as IBlast__YieldMode, GasMode} from "./interfaces/IBlast.sol";
import {IBlastPoints} from "./interfaces/IBlastPoints.sol";
import {IERC20Rebasing, YieldMode as IERC20Rebasing__YieldMode} from "./interfaces/IERC20Rebasing.sol";
abstract contract Game is ReentrancyGuard, LowLevelWETH, LowLevelERC20Transfer, VRFConsumerBaseV2, OwnableTwoSteps {
struct Game__GameParams {
uint40 blockNumber;
uint40 randomnessRequestedAt;
uint16 numberOfRounds;
address currency;
uint256 playAmountPerRound;
int256 stopGain;
int256 stopLoss;
uint256 vrfFee;
uint256 requestId;
uint256 protocolFeeBasisPoints;
uint256 liquidityPoolFeeBasisPoints;
}
/**
* @dev Fees charged to winners
* @param protocolFee The protocol fee charged to the winner
* @param liquidityPoolFee The liquidity pool fee charged to the winner
*/
struct Fee {
uint256 protocolFee;
uint256 liquidityPoolFee;
}
/**
* @dev Running game state
* @param numberOfRoundsPlayed The number of rounds played by the player so far
* @param randomWord The random word after the last round
* @param netAmount The net amount after the last round
* @param payout The payout amount after the last round
* @param payouts The payouts of each round
*/
struct RunningGameState {
uint256 numberOfRoundsPlayed;
uint256 randomWord;
int256 netAmount;
uint256 payout;
uint256[] payouts;
}
/**
* @notice The address of the GameConfigurationManager contract.
*/
IGameConfigurationManager public immutable GAME_CONFIGURATION_MANAGER;
/**
* @notice The address of the TransferManager contract.
*/
ITransferManager public immutable TRANSFER_MANAGER;
/**
* @notice The address of the WETH contract.
*/
address internal immutable WETH;
/**
* @notice The address of the USDB contract.
*/
address internal immutable USDB;
/**
* @notice The randomness requests mapping.
*/
mapping(uint256 requestId => address requester) public randomnessRequests;
event Game__Refunded(uint256 blockNumber, address player, uint256 totalPlayAmount);
error Game__InexactNativeTokensSupplied();
error Game__InvalidMultiplier();
error Game__InvalidStops();
error Game__InvalidValue();
error Game__ZeroKellyFraction();
error Game__LiquidityPoolConnected();
error Game__LiquidityPoolPaused();
error Game__NoLiquidityPool();
error Game__NoOngoingRound();
error Game__NoPendingRandomnessRequest();
error Game__OngoingRound();
error Game__PlayAmountPerRoundTooHigh();
error Game__PlayAmountPerRoundTooLow();
error Game__TooEarlyForARefund();
error Game__TooManyRounds();
error Game__WrongVrfCoordinator();
error Game__ZeroMultiplier();
error Game__ZeroNumberOfRounds();
error Game__ZeroPlayAmountPerRound();
/**
* @param _gameConfigurationManager Liquidity manager address
* @param _transferManager Transfer manager address
* @param _weth WETH address
* @param _vrfCoordinator The address of the VRF coordinator for Chainlink VRF. It is set as our VRF coordinator adapter for Gelato.
* @param _blast Blast precompile
* @param _usdb USDB address
* @param _owner The owner of the contract
* @param _blastPoints Blast points
* @param _blastPointsOperator Blast points operator
*/
constructor(
address _gameConfigurationManager,
address _transferManager,
address _weth,
address _vrfCoordinator,
address _blast,
address _usdb,
address _owner,
address _blastPoints,
address _blastPointsOperator
) VRFConsumerBaseV2(_vrfCoordinator) OwnableTwoSteps(_owner) {
GAME_CONFIGURATION_MANAGER = IGameConfigurationManager(_gameConfigurationManager);
TRANSFER_MANAGER = ITransferManager(_transferManager);
WETH = _weth;
USDB = _usdb;
(address coordinator, , , , , ) = GAME_CONFIGURATION_MANAGER.vrfParameters();
if (coordinator != _vrfCoordinator) {
revert Game__WrongVrfCoordinator();
}
IBlast(_blast).configure(IBlast__YieldMode.CLAIMABLE, GasMode.CLAIMABLE, _owner);
IBlastPoints(_blastPoints).configurePointsOperator(_blastPointsOperator);
IERC20Rebasing(_usdb).configure(IERC20Rebasing__YieldMode.CLAIMABLE);
}
/**
* @notice Claim Blast yield. Only callable by contract owner.
* @param receiver Receiver of the yield
*/
function claimYield(address receiver) external onlyOwner {
IERC20Rebasing rebasingAsset = IERC20Rebasing(USDB);
uint256 claimableAmount = rebasingAsset.getClaimableAmount(address(this));
if (claimableAmount != 0) {
rebasingAsset.claim(receiver, claimableAmount);
}
}
/**
* @notice Transfer the play amount to the liquidity pool
* @param currency The address of the currency to transfer
* @param amount The amount to transfer
*/
function _transferPlayAmountToPool(address currency, uint256 amount) internal {
address liquidityPool = _getGameLiquidityPool(currency);
if (currency == address(0)) {
_transferETHAndWrapIfFailWithGasLimit(WETH, liquidityPool, amount, gasleft());
} else {
_executeERC20DirectTransfer(currency, liquidityPool, amount);
}
}
/**
* @notice Get the game's liquidity pool with the given currency, revert if not found.
* @param currency The liquidity pool currency
*/
function _getGameLiquidityPool(address currency) internal view returns (address liquidityPool) {
liquidityPool = GAME_CONFIGURATION_MANAGER.getGameLiquidityPool(address(this), currency);
if (liquidityPool == address(0)) {
revert Game__NoLiquidityPool();
}
}
/**
* @notice Get the game's liquidity pool balance with the given currency
* @param currency The liquidity pool currency
* @return balance The liquidity pool balance
*/
function _liquidityPoolBalance(address currency) internal view returns (uint256 balance) {
address liquidityPool = _getGameLiquidityPool(currency);
if (currency == address(0)) {
currency = WETH;
}
uint256 pendingWithdrawals = LiquidityPoolRouter(payable(ILiquidityPool(liquidityPool).LIQUIDITY_POOL_ROUTER()))
.pendingWithdrawals(liquidityPool);
balance = IERC20(currency).balanceOf(liquidityPool);
if (balance >= pendingWithdrawals) {
balance -= pendingWithdrawals;
} else {
balance = 0;
}
}
/**
* @notice Escrow the play amount for the given number of rounds
* @param currency The currency to play with
* @param numberOfRounds The number of rounds to play
* @param playAmountPerRound The amount to play per round
* @param vrfFee The VRF fee to charge
*/
function _escrowPlayAmountAndChargeVrfFee(
address currency,
uint256 numberOfRounds,
uint256 playAmountPerRound,
uint256 vrfFee
) internal {
if (currency == address(0)) {
if (msg.value != playAmountPerRound * numberOfRounds + vrfFee) {
revert Game__InexactNativeTokensSupplied();
}
} else {
if (msg.value != vrfFee) {
revert Game__InexactNativeTokensSupplied();
}
TRANSFER_MANAGER.transferERC20(currency, msg.sender, address(this), playAmountPerRound * numberOfRounds);
}
}
/**
* @dev We don't check for duplicated request IDs because Gelato's Chainlink adapter increments the request ID up to uint256 max.
* For Chainlink VRF, the request ID is a hash and the probability of collision is negligible.
*
* @return fee The VRF fee
* @return requestId The request ID
*/
function _requestRandomness() internal returns (uint256 fee, uint256 requestId) {
(
address coordinator,
uint64 subscriptionId,
uint32 callbackGasLimit,
uint16 minimumRequestConfirmations,
uint240 vrfFee,
bytes32 keyHash
) = GAME_CONFIGURATION_MANAGER.vrfParameters();
requestId = VRFCoordinatorV2Interface(coordinator).requestRandomWords({
keyHash: keyHash,
subId: subscriptionId,
minimumRequestConfirmations: minimumRequestConfirmations,
callbackGasLimit: callbackGasLimit,
numWords: uint32(1)
});
randomnessRequests[requestId] = msg.sender;
fee = vrfFee;
}
/**
* @dev Add unplayed amount to the payout, transfer the total play amount to the liquidity pool, and transfer the final payout to the player.
* @param player The player address
* @param params The game parameters
* @param numberOfRoundsPlayed The number of rounds played by the player
* @param payout The payout amount
* @param protocolFee The protocol fee
*/
function _handlePayout(
address player,
Game__GameParams storage params,
uint256 numberOfRoundsPlayed,
uint256 payout,
uint256 protocolFee
) internal {
payout += params.playAmountPerRound * (params.numberOfRounds - numberOfRoundsPlayed);
_transferPlayAmountToPool(params.currency, params.playAmountPerRound * params.numberOfRounds);
if (payout > 0) {
GAME_CONFIGURATION_MANAGER.transferPayoutToPlayer(params.currency, payout, player);
}
if (protocolFee > 0) {
GAME_CONFIGURATION_MANAGER.transferProtocolFee(params.currency, protocolFee);
}
}
/**
* @dev Transfer the VRF fee to the fee recipient
*/
function _transferVrfFee(uint256 vrfFee) internal {
if (vrfFee > 0) {
_transferETHAndWrapIfFailWithGasLimit(
WETH,
GAME_CONFIGURATION_MANAGER.vrfFeeRecipient(),
vrfFee,
gasleft()
);
}
}
/**
* @dev Refund the player the total play amount and VRF fee if any
*
* @param params The game parameters
*/
function _refund(Game__GameParams storage params) internal {
if (params.numberOfRounds == 0) {
revert Game__NoOngoingRound();
}
if (params.randomnessRequestedAt == 0) {
revert Game__NoPendingRandomnessRequest();
}
if (
params.randomnessRequestedAt + GAME_CONFIGURATION_MANAGER.elapsedTimeRequiredForRefund() > block.timestamp
) {
revert Game__TooEarlyForARefund();
}
address currency = params.currency;
uint256 totalPlayAmount = params.playAmountPerRound * params.numberOfRounds;
if (currency == address(0)) {
totalPlayAmount += params.vrfFee;
_transferETHAndWrapIfFailWithGasLimit(WETH, msg.sender, totalPlayAmount, gasleft());
} else {
_executeERC20DirectTransfer(currency, msg.sender, totalPlayAmount);
if (params.vrfFee > 0) {
_transferETHAndWrapIfFailWithGasLimit(WETH, msg.sender, params.vrfFee, gasleft());
}
}
emit Game__Refunded(params.blockNumber, msg.sender, totalPlayAmount);
}
/**
* @dev Validate stop gain and stop loss. They must be >= 0 and <= 0 respectively.
* @param stopGain The stop gain amount
* @param stopLoss The stop loss amount
*/
function _validateStopGainAndLoss(int256 stopGain, int256 stopLoss) internal pure {
if (stopGain < 0 || stopLoss > 0) {
revert Game__InvalidStops();
}
}
/**
* @dev Validate the number of rounds and the play amount per round. They must be > 0.
* @param numberOfRounds The number of rounds
* @param playAmountPerRound The amount to play per round
*/
function _validateNumberOfRoundsAndPlayAmountPerRound(
uint256 numberOfRounds,
uint256 playAmountPerRound
) internal view {
if (numberOfRounds == 0) {
revert Game__ZeroNumberOfRounds();
}
if (numberOfRounds > GAME_CONFIGURATION_MANAGER.maximumNumberOfRounds()) {
revert Game__TooManyRounds();
}
if (playAmountPerRound == 0) {
revert Game__ZeroPlayAmountPerRound();
}
}
/**
* @dev Validate that there is no ongoing round. When a game is completed, the game struct is reset so it should be 0.
* @param numberOfRounds The current number of rounds being played by the player
*/
function _validateNoOngoingRound(uint256 numberOfRounds) internal pure {
if (numberOfRounds > 0) {
revert Game__OngoingRound();
}
}
/**
* @dev Games cannot be played if the liquidity pool is paused,
* but the VRF for games that started the play before the pause
* is done will still be resolved as long as the liquidity pool is not disconnected.
*
* @param currency The liquidity pool's currency
*/
function _validateLiquidityPoolIsNotPaused(address currency) internal view {
Pausable liquidityPool = Pausable(_getGameLiquidityPool(currency));
if (liquidityPool.paused()) {
revert Game__LiquidityPoolPaused();
}
}
/**
*
* @param stopGain Stop gain amount
* @param stopLoss Stop loss amount
* @param netAmount Net amount after the last round
* @return isHit True if stop gain or stop loss is hit
*/
function _stopGainOrStopLossHit(
int256 stopGain,
int256 stopLoss,
int256 netAmount
) internal pure returns (bool isHit) {
isHit = (stopGain != 0 && netAmount >= stopGain) || (stopLoss != 0 && netAmount <= stopLoss);
}
/**
* @dev Check if the VRF response is not too late. There is a 5 minutes gap between the time when a VRF request
* can be fulfilled and the time when refund can be requested.
*/
function _vrfResponseIsNotTooLate(uint40 randomnessRequestedAt) internal view returns (bool executable) {
executable =
randomnessRequestedAt - 5 minutes + GAME_CONFIGURATION_MANAGER.elapsedTimeRequiredForRefund() >
block.timestamp;
}
/**
* @param currency The currency to check
*
* @return hasLiquidityPool True if the game has a connected liquidity pool for the given currency
*/
function _hasLiquidityPool(address currency) internal view returns (bool hasLiquidityPool) {
hasLiquidityPool = GAME_CONFIGURATION_MANAGER.getGameLiquidityPool(address(this), currency) != address(0);
}
/**
* @dev The minimum play amount per game is the lesser between 0.01% of the maximum play amount per game and owner set minimum play amount.
*
* @param maxPlayAmountPerGame The maximum play amount per game
* @param currency The play currency
*/
function _minPlayAmountPerGame(
uint256 maxPlayAmountPerGame,
address currency
) internal view returns (uint256 minPlayAmountPerGame) {
minPlayAmountPerGame = maxPlayAmountPerGame / 10_000;
uint256 minPlayAmountFromConfig = GAME_CONFIGURATION_MANAGER.minPlayAmountPerGame(address(this), currency);
if (minPlayAmountFromConfig < minPlayAmountPerGame && minPlayAmountFromConfig != 0) {
minPlayAmountPerGame = minPlayAmountFromConfig;
}
}
function _liquidityProviderAdjustedReturn() internal view returns (uint256) {
IGameConfigurationManager.FeeSplit memory feeSplit = GAME_CONFIGURATION_MANAGER.getFeeSplit(address(this));
return 10_000 - feeSplit.liquidityPoolFeeBasisPoints;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
enum YieldMode {
AUTOMATIC,
VOID,
CLAIMABLE
}
enum GasMode {
VOID,
CLAIMABLE
}
interface IBlast {
// configure
function configureContract(address contractAddress, YieldMode _yield, GasMode gasMode, address governor) external;
function configure(YieldMode _yield, GasMode gasMode, address governor) external;
// base configuration options
function configureClaimableYield() external;
function configureClaimableYieldOnBehalf(address contractAddress) external;
function configureAutomaticYield() external;
function configureAutomaticYieldOnBehalf(address contractAddress) external;
function configureVoidYield() external;
function configureVoidYieldOnBehalf(address contractAddress) external;
function configureClaimableGas() external;
function configureClaimableGasOnBehalf(address contractAddress) external;
function configureVoidGas() external;
function configureVoidGasOnBehalf(address contractAddress) external;
function configureGovernor(address _governor) external;
function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external;
// claim yield
function claimYield(address contractAddress, address recipientOfYield, uint256 amount) external returns (uint256);
function claimAllYield(address contractAddress, address recipientOfYield) external returns (uint256);
// claim gas
function claimAllGas(address contractAddress, address recipientOfGas) external returns (uint256);
function claimGasAtMinClaimRate(
address contractAddress,
address recipientOfGas,
uint256 minClaimRateBips
) external returns (uint256);
function claimMaxGas(address contractAddress, address recipientOfGas) external returns (uint256);
function claimGas(
address contractAddress,
address recipientOfGas,
uint256 gasToClaim,
uint256 gasSecondsToConsume
) external returns (uint256);
// read functions
function readClaimableYield(address contractAddress) external view returns (uint256);
function readYieldConfiguration(address contractAddress) external view returns (uint8);
function readGasParams(
address contractAddress
) external view returns (uint256 etherSeconds, uint256 etherBalance, uint256 lastUpdated, GasMode);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IBlastPoints {
function configurePointsOperator(address operator) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
enum YieldMode {
AUTOMATIC,
VOID,
CLAIMABLE
}
interface IERC20Rebasing {
// changes the yield mode of the caller and update the balance
// to reflect the configuration
function configure(YieldMode) external returns (uint256);
// "claimable" yield mode accounts can call this this claim their yield
// to another address
function claim(address recipient, uint256 amount) external returns (uint256);
// read the claimable amount for an account
function getClaimableAmount(address account) external view returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IGameConfigurationManager {
/**
* @notice The fee split structure between the protocol and the liquidity pool for each game
* @param protocolFeeBasisPoints The protocol fee basis points
* @param liquidityPoolFeeBasisPoints The liquidity wpoolprovider fee basis points
*/
struct FeeSplit {
uint16 protocolFeeBasisPoints;
uint16 liquidityPoolFeeBasisPoints;
}
/**
* @notice Initiate a connection request between a game and a liquidity pool. A game cannot run without a liquidity pool. Only callable by the owner.
* @param game The game contract address
* @param currency The currency address
* @param liquidityPool The liquidity pool address
*/
function initiateGameLiquidityPoolConnectionRequest(address game, address currency, address liquidityPool) external;
/**
* @notice Confirm the connection request between a game and a liquidity pool.
* @param game The game contract address
* @param currency The currency address
* @param liquidityPool The liquidity pool address
*/
function confirmGameLiquidityPoolConnectionRequest(address game, address currency, address liquidityPool) external;
/**
* @notice Disconnect a game from a liquidity pool. Only callable by the owner.
* @param game The game contract address
* @param currency The currency address
*/
function disconnectGameFromLiquidityPool(address game, address currency) external;
/**
* @notice The time required for a refund to be available
*/
function elapsedTimeRequiredForRefund() external view returns (uint40);
/**
* @notice VRF Fee recipient address
* @return The VRF fee recipient address
*/
function vrfFeeRecipient() external view returns (address);
/**
* @notice Protocol fee recipient address
* @return The protocol fee recipient address
*/
function protocolFeeRecipient() external view returns (address);
/**
* @notice Get the game's liquidity pool with the given currency
* @param game Game contract address
* @param currency The liquidity pool's currency
* @return liquidityPool The liquidity pool address
*/
function getGameLiquidityPool(address game, address currency) external view returns (address liquidityPool);
/**
* @notice Get the fee split between the protocol and the liquidity pool for the given game
* @param game The game contract address
* @return The fee split struct between the protocol and the liquidity pool for the given game
*/
function getFeeSplit(address game) external view returns (FeeSplit memory);
/**
* @notice Each game has its own way of calculating the optimial Kelly fraction. If we want to lower the Kelly fraction for a game,
* we can adjust it to a percentage of the optimal Kelly fraction. The valid value is between 0 and 10,000 (100%).
* @return kellyFraction The optimal Kelly fraction in basis points
*/
function kellyFractionBasisPoints(address game) external view returns (uint256 kellyFraction);
/**
* @notice Return the minimum play amount of any given game in the given currency.
* The game should choose the lesser between this value and the max amount per game divided by 10,000.
* @param game The game contract address
* @param currency The play currency
*/
function minPlayAmountPerGame(address game, address currency) external view returns (uint256 amount);
/**
* @notice The maximum number of rounds a player can enter each time
* @return The maximum number of rounds
*/
function maximumNumberOfRounds() external view returns (uint16);
/**
* @notice Set the proportion of the optimal Kelly fraction for the given game. Only callable by the owner.
* @param game The game contract address
* @param basisPoints The proportion of the optimal Kelly fraction in basis points
*/
function setGameKellyFractionBasisPoints(address game, uint256 basisPoints) external;
/**
* @notice Set the proportion of the optimal Kelly fraction for the given game. Only callable by the owner.
* @param game The game contract address
* @param currency The play currency
* @param amount The minimum play amount
*/
function setMinPlayAmountPerGame(address game, address currency, uint256 amount) external;
/**
* @notice Transfer game payout to the player. Only callable a connected game contract.
* @param currency The currency address
* @param amount The amount to transfer
* @param receiver The receiver address
*/
function transferPayoutToPlayer(address currency, uint256 amount, address receiver) external;
/**
* @notice Transfer protocol fee. Only callable a connected game contract.
* @param currency The currency address
* @param amount The amount to transfer
*/
function transferProtocolFee(address currency, uint256 amount) external;
/**
* @notice VRF parameters stores the necessary information for making VRF requests.
*/
function vrfParameters()
external
view
returns (
address coordinator,
uint64 subscriptionId,
uint32 callbackGasLimit,
uint16 minimumRequestConfirmations,
uint240 vrfFee,
bytes32 keyHash
);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface ILiquidityPool {
event PayoutTransferred(address game, address receiver, address currency, uint256 amount);
event InsufficientFundsForPayout(address game, address receiver, address currency, uint256 shortfall);
event ProtocolFeeTransferred(address game, address protocolFeeRecipient, address currency, uint256 amount);
error LiquidityPool__NotAuthorized();
error LiquidityPool__UnsupportedOperation();
/**
* @notice Returns the liquidity pool router contract address
*/
function LIQUIDITY_POOL_ROUTER() external view returns (address);
/**
* @notice Transfer payout to the player, only the game configuration manager can call this function
* @param game Game contract address
* @param amount Amount to transfer
* @param receiver Prize receiver
*/
function transferPayoutToPlayer(address game, uint256 amount, address receiver) external;
/**
* @notice Transfer protocol fee, only the game configuration manager can call this function
* @param game Game contract address
* @param amount Amount to transfer
* @param protocolFeeRecipient Protocol fee recipient
*/
function transferProtocolFee(address game, uint256 amount, address protocolFeeRecipient) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {OwnableTwoSteps} from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol";
import {ReentrancyGuard} from "@looksrare/contracts-libs/contracts/ReentrancyGuard.sol";
import {Pausable} from "@looksrare/contracts-libs/contracts/Pausable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ILiquidityPool} from "./interfaces/ILiquidityPool.sol";
import {IBlast, YieldMode, GasMode} from "./interfaces/IBlast.sol";
import {IBlastPoints} from "./interfaces/IBlastPoints.sol";
import {IERC20Rebasing} from "./interfaces/IERC20Rebasing.sol";
import {LowLevelERC20Transfer} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol";
/**
* @title LiquidityPool
* @notice This contract allows depositors to act as the counterparty of players of our games.
* @author YOLO Games protocol team
*/
abstract contract LiquidityPool is
ERC4626,
OwnableTwoSteps,
ReentrancyGuard,
Pausable,
ILiquidityPool,
LowLevelERC20Transfer
{
/**
* @notice Liquidity manager contract address
*/
address public immutable GAME_CONFIGURATION_MANAGER;
/**
* @notice Liquidity pool router contract address
*/
address public immutable LIQUIDITY_POOL_ROUTER;
/**
* @param _name Vault share name
* @param _symbol Vault share symbol
* @param _owner Owner of the contract
* @param _asset Vault asset contract address
* @param _gameConfigurationManager Liquidity manager contract address
* @param _liquidityPoolRouter Liquidity pool router contract address
* @param _blast Blast precompile
* @param _blastPoints Blast points
* @param _blastPointsOperator Blast points operator
*/
constructor(
string memory _name,
string memory _symbol,
address _owner,
address _asset,
address _gameConfigurationManager,
address _liquidityPoolRouter,
address _blast,
address _blastPoints,
address _blastPointsOperator
) ERC20(_name, _symbol) ERC4626(IERC20(_asset)) OwnableTwoSteps(_owner) {
GAME_CONFIGURATION_MANAGER = _gameConfigurationManager;
LIQUIDITY_POOL_ROUTER = _liquidityPoolRouter;
IBlast(_blast).configure(YieldMode.CLAIMABLE, GasMode.CLAIMABLE, _owner);
IBlastPoints(_blastPoints).configurePointsOperator(_blastPointsOperator);
}
/**
* @inheritdoc ERC4626
*/
function deposit(uint256 assets, address receiver) public override returns (uint256) {
_onlyLiquidityPoolRouter();
return super.deposit(assets, receiver);
}
/**
* @inheritdoc ERC4626
*/
function redeem(uint256 shares, address receiver, address owner) public override returns (uint256) {
_onlyLiquidityPoolRouter();
return super.redeem(shares, receiver, owner);
}
/**
* @notice Mint is not supported by this contract
*/
function mint(uint256, address) public pure override returns (uint256) {
revert LiquidityPool__UnsupportedOperation();
}
/**
* @notice Withdraw is not supported by this contract
*/
function withdraw(uint256, address, address) public pure override returns (uint256) {
revert LiquidityPool__UnsupportedOperation();
}
/**
* @notice Toggle paused state. Only callable by contract owner.
*/
function togglePaused() external onlyOwner {
paused() ? _unpause() : _pause();
}
/**
* @notice Claim Blast yield. Only callable by contract owner.
* @param receiver Receiver of the yield
*/
function claimYield(address receiver) external onlyOwner {
IERC20Rebasing rebasingAsset = IERC20Rebasing(asset());
uint256 claimableAmount = rebasingAsset.getClaimableAmount(address(this));
if (claimableAmount != 0) {
rebasingAsset.claim(receiver, claimableAmount);
}
}
function _decimalsOffset() internal pure override returns (uint8) {
return 6;
}
function _onlyGameConfigurationManager() internal view {
if (msg.sender != GAME_CONFIGURATION_MANAGER) {
revert LiquidityPool__NotAuthorized();
}
}
function _onlyLiquidityPoolRouter() internal view {
if (msg.sender != LIQUIDITY_POOL_ROUTER) {
revert LiquidityPool__NotAuthorized();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ITransferManager} from "@looksrare/contracts-transfer-manager/contracts/interfaces/ITransferManager.sol";
import {OwnableTwoSteps} from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol";
import {LowLevelWETH} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelWETH.sol";
import {LowLevelERC20Transfer} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol";
import {IWETH} from "@looksrare/contracts-libs/contracts/interfaces/generic/IWETH.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@looksrare/contracts-libs/contracts/ReentrancyGuard.sol";
import {Pausable} from "@looksrare/contracts-libs/contracts/Pausable.sol";
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {IBlast, YieldMode as IBlast__YieldMode, GasMode as IBlast__GasMode} from "./interfaces/IBlast.sol";
import {IERC20Rebasing, YieldMode as IERC20Rebasing__YieldMode} from "./interfaces/IERC20Rebasing.sol";
import {IBlastPoints} from "./interfaces/IBlastPoints.sol";
import {LiquidityPool} from "./LiquidityPool.sol";
/**
* @title LiquidityPoolRouter
* @notice All liquidity pool related operations must be routed through this contract.
* @author YOLO Games protocol team
*/
contract LiquidityPoolRouter is OwnableTwoSteps, LowLevelWETH, LowLevelERC20Transfer, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
event LiquidityPoolRouter__DepositInitialized(
address user,
address liquidityPool,
uint256 amount,
uint256 expectedShares,
uint256 finalizationIncentive
);
event LiquidityPoolRouter__DepositFinalized(
address caller,
address user,
address liquidityPool,
uint256 amount,
uint256 sharesMinted
);
event LiquidityPoolRouter__DepositLimitUpdated(
address liquidityPool,
uint256 minDepositAmount,
uint256 maxDepositAmount,
uint256 maxBalance
);
event LiquidityPoolRouter__FinalizationParamsUpdated(
uint256 timelockDelay,
uint256 finalizationForAllDelay,
uint256 finalizationIncentive
);
event LiquidityPoolRouter__LiquidityPoolAdded(address token, address liquidityPool);
event LiquidityPoolRouter__RedemptionInitialized(
address user,
address liquidityPool,
uint256 amount,
uint256 expectedAssets,
uint256 finalizationIncentive
);
event LiquidityPoolRouter__RedemptionFinalized(
address caller,
address user,
address liquidityPool,
uint256 amount,
uint256 assetsRedeemed
);
error LiquidityPoolRouter__DepositAmountTooHigh();
error LiquidityPoolRouter__DepositAmountTooLow();
error LiquidityPoolRouter__FinalizationForAllIsNotOpen();
error LiquidityPoolRouter__FinalizationIncentiveNotPaid();
error LiquidityPoolRouter__FinalizationIncentiveTooHigh();
error LiquidityPoolRouter__FinalizationForAllDelayTooHigh();
error LiquidityPoolRouter__FinalizationForAllDelayTooLow();
error LiquidityPoolRouter__InvalidTimelockDelay();
error LiquidityPoolRouter__MaxDepositAmountTooHigh();
error LiquidityPoolRouter__MinDepositAmountTooHigh();
error LiquidityPoolRouter__NoLiquidityPoolForToken();
error LiquidityPoolRouter__NoOngoingDeposit();
error LiquidityPoolRouter__NoOngoingRedemption();
error LiquidityPoolRouter__OngoingDeposit();
error LiquidityPoolRouter__OngoingRedemption();
error LiquidityPoolRouter__TimelockIsNotOver();
error LiquidityPoolRouter__TokenAlreadyHasLiquidityPool();
error LiquidityPoolRouter__WETHDepositNotAllowed();
error LiquidityPoolRouter__ZeroExpectedAssets();
error LiquidityPoolRouter__ZeroExpectedShares();
/**
* @notice Deposit struct
* @param liquidityPool The liquidity pool address
* @param amount The asset deposit amount
* @param expectedShares The expected shares to be minted during initialization
* @param initializedAt The timestamp when the deposit was initialized
* @param finalizationIncentive The finalization incentive for the deposit
*/
struct Deposit {
address liquidityPool;
uint256 amount;
uint256 expectedShares;
uint256 initializedAt;
uint256 finalizationIncentive;
}
/**
* @notice Redemption struct
* @param liquidityPool The liquidity pool address
* @param shares The shares to be redeemed
* @param expectedAssets The expected assets to be redeemed during initialization
* @param initializedAt The timestamp when the redemption was initialized
* @param finalizationIncentive The finalization incentive for the redemption
*/
struct Redemption {
address liquidityPool;
uint256 shares;
uint256 expectedAssets;
uint256 initializedAt;
uint256 finalizationIncentive;
}
/**
* @notice DepositLimit struct
* @param minDepositAmount Minimum deposit amount per transaction
* @param maxDepositAmount Maximum deposit amount per transaction
* @param maxBalance Liquidity pool max balance (only prevents further deposits)
*/
struct DepositLimit {
uint256 minDepositAmount;
uint256 maxDepositAmount;
uint256 maxBalance;
}
/**
* @notice FinalizationParams struct
*
* @param timelockDelay Time lock for the 2 steps deposit/withdrawal process
* @param finalizationForAllDelay Time lock until a deposit's finalization is open to all
* @param finalizationIncentive The router incentivizes bots that backstop finalization if the depositor/redeemer does not complete the process
*/
struct FinalizationParams {
uint80 timelockDelay;
uint80 finalizationForAllDelay;
uint80 finalizationIncentive;
}
/**
* @notice We charge a fee of 0.5% on deposits.
*/
uint256 public constant DEPOSIT_FEE_BASIS_POINTS = 50;
/**
* @notice WETH contract address
*/
address public immutable WETH;
/**
* @notice USDB contract address
*/
address public immutable USDB;
/**
* @notice Transfer manager contract address
*/
ITransferManager public immutable TRANSFER_MANAGER;
/**
* @notice liquidityPools keeps track of supported tokens and their corresponding liquidity pools
*
* @dev ETH liquidity pool should use the address of WETH as the key
*/
mapping(address token => address liquidityPool) public liquidityPools;
/**
* @notice DepositLimit keeps track of the deposit limits for each liquidity pool
*/
mapping(address liquidityPool => DepositLimit) public depositLimit;
/**
* @notice deposits keeps track of each address's pending deposit
*/
mapping(address account => Deposit) public deposits;
/**
* @notice redemptions keeps track of each address's pending share redemption
*/
mapping(address account => Redemption) public redemptions;
/**
* @notice pendingDeposits keeps track of each liquidity pool's pending deposit amount
*/
mapping(address liquidityPool => uint256 amount) public pendingDeposits;
/**
* @notice pendingWithdrawals keeps track of each liquidity pool's pending withdrawal amount
*/
mapping(address liquidityPool => uint256 amount) public pendingWithdrawals;
FinalizationParams public finalizationParams;
/**
* @param _owner The owner of the contract
* @param _weth WETH contract address
* @param _usdb USDB contract address
* @param _transferManager Transfer manager contract address
* @param _blast Blast precompile
* @param _blastPoints Blast points
* @param _blastPointsOperator Blast points operator
*/
constructor(
address _owner,
address _weth,
address _usdb,
address _transferManager,
address _blast,
address _blastPoints,
address _blastPointsOperator
) OwnableTwoSteps(_owner) {
WETH = _weth;
USDB = _usdb;
TRANSFER_MANAGER = ITransferManager(_transferManager);
_setFinalizationParams(10 seconds, 5 minutes, 0.0003 ether);
IBlast(_blast).configure(IBlast__YieldMode.CLAIMABLE, IBlast__GasMode.CLAIMABLE, _owner);
IERC20Rebasing(_weth).configure(IERC20Rebasing__YieldMode.CLAIMABLE);
IERC20Rebasing(_usdb).configure(IERC20Rebasing__YieldMode.CLAIMABLE);
IBlastPoints(_blastPoints).configurePointsOperator(_blastPointsOperator);
}
/**
* @notice Support a new liquidity pool. Only callable by the owner.
*
* @param liquidityPool Liquidity pool address
*/
function addLiquidityPool(address liquidityPool) external onlyOwner {
address token = ERC4626(liquidityPool).asset();
if (liquidityPools[token] != address(0)) {
revert LiquidityPoolRouter__TokenAlreadyHasLiquidityPool();
}
liquidityPools[token] = liquidityPool;
emit LiquidityPoolRouter__LiquidityPoolAdded(token, liquidityPool);
}
/**
* @notice Update the deposit limits for a liquidity pool. Only callable by the owner.
*
* @param liquidityPool The liquidity pool's contract address
* @param minDepositAmount The minimum deposit amount per transaction
* @param maxDepositAmount The maximum deposit amount per transaction
* @param maxBalance The liquidity pool's max balance
*/
function setDepositLimit(
address liquidityPool,
uint256 minDepositAmount,
uint256 maxDepositAmount,
uint256 maxBalance
) external onlyOwner {
if (minDepositAmount > maxDepositAmount) {
revert LiquidityPoolRouter__MinDepositAmountTooHigh();
}
if (maxBalance < maxDepositAmount) {
revert LiquidityPoolRouter__MaxDepositAmountTooHigh();
}
depositLimit[liquidityPool] = DepositLimit(minDepositAmount, maxDepositAmount, maxBalance);
emit LiquidityPoolRouter__DepositLimitUpdated(liquidityPool, minDepositAmount, maxDepositAmount, maxBalance);
}
/**
* @notice Update finalization params. Only callable by the owner.
*
* @param _timelockDelay The new timelock delay
* @param _finalizationForAllDelay The new finalization for all delay
* @param _finalizationIncentive The new finalization incentive
*/
function setFinalizationParams(
uint80 _timelockDelay,
uint80 _finalizationForAllDelay,
uint80 _finalizationIncentive
) external onlyOwner {
_setFinalizationParams(_timelockDelay, _finalizationForAllDelay, _finalizationIncentive);
}
/**
* @notice First step of the 2 steps deposit process. Commit ETH into the pool
* and estimate the expected amount of shares to be minted.
*
* @param amount The deposit amount
*/
function depositETH(uint256 amount) external payable nonReentrant whenNotPaused {
address liquidityPool = _getLiquidityPoolOrRevert(WETH);
if (amount + finalizationParams.finalizationIncentive != msg.value) {
revert LiquidityPoolRouter__FinalizationIncentiveNotPaid();
}
uint256 depositFee = (amount * DEPOSIT_FEE_BASIS_POINTS) / 10_000;
amount -= depositFee;
_validateDepositAmount(liquidityPool, amount);
if (deposits[msg.sender].amount != 0) {
revert LiquidityPoolRouter__OngoingDeposit();
}
_transferETHAndWrapIfFailWithGasLimit(WETH, owner, depositFee, gasleft());
uint256 expectedShares = ERC4626(liquidityPool).previewDeposit(amount);
if (expectedShares == 0) {
revert LiquidityPoolRouter__ZeroExpectedShares();
}
deposits[msg.sender] = Deposit(
liquidityPool,
amount,
expectedShares,
block.timestamp,
finalizationParams.finalizationIncentive
);
pendingDeposits[liquidityPool] += amount;
emit LiquidityPoolRouter__DepositInitialized(
msg.sender,
liquidityPool,
amount + depositFee,
expectedShares,
finalizationParams.finalizationIncentive
);
}
/**
* @notice First step of the 2 steps deposit process. Commit ERC-20 token into the pool
* and estimate the expected amount of shares to be minted.
*
* @param token The deposit token
* @param amount The deposit amount
*/
function deposit(address token, uint256 amount) external payable nonReentrant whenNotPaused {
if (token == WETH) {
revert LiquidityPoolRouter__WETHDepositNotAllowed();
}
address liquidityPool = _getLiquidityPoolOrRevert(token);
_validateFinalizationIncentivePayment();
uint256 depositFee = (amount * DEPOSIT_FEE_BASIS_POINTS) / 10_000;
amount -= depositFee;
_validateDepositAmount(liquidityPool, amount);
if (deposits[msg.sender].amount != 0) {
revert LiquidityPoolRouter__OngoingDeposit();
}
TRANSFER_MANAGER.transferERC20(token, msg.sender, address(this), amount);
TRANSFER_MANAGER.transferERC20(token, msg.sender, owner, depositFee);
uint256 expectedShares = ERC4626(liquidityPool).previewDeposit(amount);
if (expectedShares == 0) {
revert LiquidityPoolRouter__ZeroExpectedShares();
}
deposits[msg.sender] = Deposit(
liquidityPool,
amount,
expectedShares,
block.timestamp,
finalizationParams.finalizationIncentive
);
pendingDeposits[liquidityPool] += amount;
emit LiquidityPoolRouter__DepositInitialized(
msg.sender,
liquidityPool,
amount + depositFee,
expectedShares,
finalizationParams.finalizationIncentive
);
}
/**
* @notice Finalize a deposit after the first step. Mint shares to the depositor.
* If the expected shares are more than the actual shares, just mint the actual shares.
* If the expected shares are less than the actual shares, mint the expected shares and
* send the remaining ETH back to the liquidity pool.
*
* @param depositor The depositor address
*/
function finalizeDeposit(address depositor) external nonReentrant {
uint256 amount = deposits[depositor].amount;
if (amount == 0) {
revert LiquidityPoolRouter__NoOngoingDeposit();
}
uint256 initializedAt = deposits[depositor].initializedAt;
_validateTimelockIsOver(initializedAt);
_validateFinalizationIsOpenForAll(depositor, initializedAt);
address payable liquidityPool = payable(deposits[depositor].liquidityPool);
address token = ERC4626(liquidityPool).asset();
uint256 expectedShares = deposits[depositor].expectedShares;
uint256 actualShares = ERC4626(liquidityPool).previewDeposit(amount);
uint256 incentive = deposits[depositor].finalizationIncentive;
deposits[depositor] = Deposit(address(0), 0, 0, 0, 0);
pendingDeposits[liquidityPool] -= amount;
_transferETHAndWrapIfFailWithGasLimit(WETH, msg.sender, incentive, 2_300);
uint256 sharesMinted;
uint256 amountRequired;
if (expectedShares >= actualShares) {
amountRequired = amount;
sharesMinted = _deposit(token, liquidityPool, amountRequired, depositor);
} else {
amountRequired = ERC4626(liquidityPool).previewMint(expectedShares);
sharesMinted = _deposit(token, liquidityPool, amountRequired, depositor);
if (token == WETH) {
_transferETHAndWrapIfFailWithGasLimit(WETH, liquidityPool, amount - amountRequired, gasleft());
} else {
_executeERC20DirectTransfer(token, liquidityPool, amount - amountRequired);
}
}
emit LiquidityPoolRouter__DepositFinalized(msg.sender, depositor, liquidityPool, amountRequired, sharesMinted);
}
/**
* @notice First step of the 2 steps redemption process. Commit vault shares into the pool
* and estimate the expected amount of assets to redeem.
*
* @param token The token to redeem
* @param amount The redemption amount
*/
function redeem(address token, uint256 amount) external payable nonReentrant {
address liquidityPool = _getLiquidityPoolOrRevert(token);
_validateFinalizationIncentivePayment();
if (redemptions[msg.sender].shares != 0) {
revert LiquidityPoolRouter__OngoingRedemption();
}
TRANSFER_MANAGER.transferERC20(liquidityPool, msg.sender, address(this), amount);
uint256 expectedAssets = ERC4626(liquidityPool).previewRedeem(amount);
if (expectedAssets == 0) {
revert LiquidityPoolRouter__ZeroExpectedAssets();
}
redemptions[msg.sender] = Redemption(
liquidityPool,
amount,
expectedAssets,
block.timestamp,
finalizationParams.finalizationIncentive
);
pendingWithdrawals[liquidityPool] += expectedAssets;
emit LiquidityPoolRouter__RedemptionInitialized(
msg.sender,
liquidityPool,
amount,
expectedAssets,
finalizationParams.finalizationIncentive
);
}
/**
* @notice Finalize a redemption after the first step. Redeem shares for the redeemer.
* If the expected assets are more than the actual assets, send the actual assets to the redeemer.
* If the expected assets are less than the actual assets, send the expected shares to the redeemer and
* send the remaining ETH back to the liquidity pool.
*
* @param redeemer The redeemer address
*/
function finalizeRedemption(address redeemer) external nonReentrant {
uint256 amount = redemptions[redeemer].shares;
if (amount == 0) {
revert LiquidityPoolRouter__NoOngoingRedemption();
}
uint256 initializedAt = redemptions[redeemer].initializedAt;
_validateTimelockIsOver(initializedAt);
_validateFinalizationIsOpenForAll(redeemer, initializedAt);
address payable liquidityPool = payable(redemptions[redeemer].liquidityPool);
address token = ERC4626(liquidityPool).asset();
uint256 expectedAssets = redemptions[redeemer].expectedAssets;
uint256 incentive = redemptions[redeemer].finalizationIncentive;
redemptions[redeemer] = Redemption(address(0), 0, 0, 0, 0);
pendingWithdrawals[liquidityPool] -= expectedAssets;
uint256 assetsRedeemed = LiquidityPool(liquidityPool).redeem(amount, address(this), address(this));
_transferETHAndWrapIfFailWithGasLimit(WETH, msg.sender, incentive, 2_300);
if (expectedAssets >= assetsRedeemed) {
_transferAssetsRedeemed(token, redeemer, assetsRedeemed);
} else {
_transferAssetsRedeemed(token, redeemer, expectedAssets);
address receiver = LiquidityPool(liquidityPool).totalSupply() == 0 ? owner : liquidityPool;
_executeERC20DirectTransfer(token, receiver, assetsRedeemed - expectedAssets);
assetsRedeemed = expectedAssets;
}
emit LiquidityPoolRouter__RedemptionFinalized(msg.sender, redeemer, liquidityPool, amount, assetsRedeemed);
}
/**
* @notice Toggle paused state. Only callable by contract owner.
*/
function togglePaused() external onlyOwner {
paused() ? _unpause() : _pause();
}
/**
* @notice Claim Blast yield. Only callable by contract owner.
* @param receiver Receiver of the yield
*/
function claimYield(address receiver) external onlyOwner {
_claimYield(WETH, receiver);
_claimYield(USDB, receiver);
}
receive() external payable {}
/**
* @dev Wrap ETH, approve the liquidity pool to spend the wrapped ETH, and deposit the wrapped ETH
*
* @param token The token address
* @param liquidityPool The liquidity pool address
* @param amount The deposit amount
* @param depositor The depositor address
*/
function _deposit(
address token,
address liquidityPool,
uint256 amount,
address depositor
) private returns (uint256 sharesMinted) {
if (token == WETH) {
IWETH(WETH).deposit{value: amount}();
}
IERC20(token).forceApprove(liquidityPool, amount);
sharesMinted = LiquidityPool(liquidityPool).deposit(amount, depositor);
}
/**
* @dev Transfer the assets redeemed to the redeemer
*
* @param token The token address
* @param redeemer The redeemer address
* @param assetsRedeemed The assets redeemed
*/
function _transferAssetsRedeemed(address token, address redeemer, uint256 assetsRedeemed) private {
if (token == WETH) {
IWETH(WETH).withdraw(assetsRedeemed);
_transferETHAndWrapIfFailWithGasLimit(WETH, redeemer, assetsRedeemed, 2_300);
} else {
_executeERC20DirectTransfer(token, redeemer, assetsRedeemed);
}
}
/**
* @notice Claim Blast yield.
*
* @param token WETH or USDB
* @param receiver Receiver of the yield
*/
function _claimYield(address token, address receiver) private {
IERC20Rebasing rebasingAsset = IERC20Rebasing(token);
uint256 claimableAmount = rebasingAsset.getClaimableAmount(address(this));
if (claimableAmount != 0) {
rebasingAsset.claim(receiver, claimableAmount);
}
}
/**
* @dev Update finalization params
*
* @param _timelockDelay The new timelock delay
* @param _finalizationForAllDelay The new finalization for all delay
* @param _finalizationIncentive The new finalization incentive
*/
function _setFinalizationParams(
uint80 _timelockDelay,
uint80 _finalizationForAllDelay,
uint80 _finalizationIncentive
) private {
if (_finalizationIncentive > 0.01 ether) {
revert LiquidityPoolRouter__FinalizationIncentiveTooHigh();
}
if (_timelockDelay < 5 seconds || _timelockDelay > 1 minutes) {
revert LiquidityPoolRouter__InvalidTimelockDelay();
}
if (_finalizationForAllDelay > 5 minutes) {
revert LiquidityPoolRouter__FinalizationForAllDelayTooHigh();
}
if (_finalizationIncentive > 0 && _finalizationForAllDelay < 30 seconds) {
revert LiquidityPoolRouter__FinalizationForAllDelayTooLow();
}
finalizationParams = FinalizationParams(_timelockDelay, _finalizationForAllDelay, _finalizationIncentive);
emit LiquidityPoolRouter__FinalizationParamsUpdated(
_timelockDelay,
_finalizationForAllDelay,
_finalizationIncentive
);
}
/**
* @dev Get the liquidity pool address for a token or revert if none is found
*
* @param token The token address
*
* @return liquidityPool The liquidity pool address
*/
function _getLiquidityPoolOrRevert(address token) private view returns (address liquidityPool) {
liquidityPool = liquidityPools[token];
if (liquidityPool == address(0)) {
revert LiquidityPoolRouter__NoLiquidityPoolForToken();
}
}
/**
* @dev Validate the deposit amount to be within the acceptable range
*
* @param liquidityPool The liquidity pool address
* @param amount The deposit amount
*/
function _validateDepositAmount(address liquidityPool, uint256 amount) private view {
if (amount == 0) {
revert LiquidityPoolRouter__DepositAmountTooLow();
}
if (amount < depositLimit[liquidityPool].minDepositAmount) {
revert LiquidityPoolRouter__DepositAmountTooLow();
}
if (amount > depositLimit[liquidityPool].maxDepositAmount) {
revert LiquidityPoolRouter__DepositAmountTooHigh();
}
if (
IERC20(ERC4626(liquidityPool).asset()).balanceOf(liquidityPool) + amount + pendingDeposits[liquidityPool] >
depositLimit[liquidityPool].maxBalance
) {
revert LiquidityPoolRouter__DepositAmountTooHigh();
}
}
/**
* @dev Validate that the deposit/redemption finalization is open for all
*
* @param requester The requester address
* @param initializedAt The timestamp when the deposit/redemption was initialized
*/
function _validateFinalizationIsOpenForAll(address requester, uint256 initializedAt) private view {
if (
msg.sender != requester &&
block.timestamp <
initializedAt + finalizationParams.timelockDelay + finalizationParams.finalizationForAllDelay
) {
revert LiquidityPoolRouter__FinalizationForAllIsNotOpen();
}
}
/**
* @dev Validate that the timelock is over
*
* @param initializedAt The timestamp when the deposit/redemption was initialized
*/
function _validateTimelockIsOver(uint256 initializedAt) private view {
if (block.timestamp < initializedAt + finalizationParams.timelockDelay) {
revert LiquidityPoolRouter__TimelockIsNotOver();
}
}
function _validateFinalizationIncentivePayment() private view {
if (msg.value != finalizationParams.finalizationIncentive) {
revert LiquidityPoolRouter__FinalizationIncentiveNotPaid();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an overflow.
error RPowOverflow();
/// @dev The mantissa is too big to fit.
error MantissaOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, due to an multiplication overflow.
error SMulWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error SDivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The input outside the acceptable domain.
error OutOfDomain();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if mul(y, gt(x, div(not(0), y))) {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, WAD)
// Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
if iszero(and(iszero(iszero(y)), eq(sdiv(z, WAD), x))) {
mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
/// Note: This function is an approximation.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is less than 0.5 we return zero.
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
if (x <= -41446531673892822313) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
if iszero(slt(x, 135305999368893231589)) {
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
revert(0x1c, 0x04)
}
}
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// `k` is in the range `[-61, 195]`.
// Evaluate using a (6, 7)-term rational approximation.
// `p` is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already `2**96` too large.
r := sdiv(p, q)
}
// r should be in the range `(0.09, 0.25) * 2**96`.
// We now need to multiply r by:
// - The scale factor `s ≈ 6.031367120`.
// - The `2**k` factor from the range reduction.
// - The `1e18 / 2**96` factor for base conversion.
// We do this all at once, with an intermediate result in `2**213`
// basis, so the final right shift is always by a positive amount.
r = int256(
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
);
}
}
/// @dev Returns `ln(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function lnWad(int256 x) internal pure returns (int256 r) {
/// @solidity memory-safe-assembly
assembly {
// We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
// We do this by multiplying by `2**96 / 10**18`. But since
// `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
// and add `ln(2**96 / 10**18)` at the end.
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// We place the check here for more optimal stack operations.
if iszero(sgt(x, 0)) {
mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
revert(0x1c, 0x04)
}
// forgefmt: disable-next-item
r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
x := shr(159, shl(r, x))
// Evaluate using a (8, 8)-term rational approximation.
// `p` is made monic, we will multiply by a scale factor later.
// forgefmt: disable-next-item
let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
sar(96, mul(add(43456485725739037958740375743393,
sar(96, mul(add(24828157081833163892658089445524,
sar(96, mul(add(3273285459638523848632254066296,
x), x))), x))), x)), 11111509109440967052023855526967)
p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
// `q` is monic by convention.
let q := add(5573035233440673466300451813936, x)
q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
q := add(909429971244387300277376558375, sar(96, mul(x, q)))
// `p / q` is in the range `(0, 0.125) * 2**96`.
// Finalization, we need to:
// - Multiply by the scale factor `s = 5.549…`.
// - Add `ln(2**96 / 10**18)`.
// - Add `k * ln(2)`.
// - Multiply by `10**18 / 2**96 = 5**18 >> 78`.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already `2**96` too large.
p := sdiv(p, q)
// Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
p := mul(1677202110996718588342820967067443963516166, p)
// Add `ln(2) * k * 5**18 * 2**192`.
// forgefmt: disable-next-item
p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
// Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
// Base conversion: mul `2**18 / 2**192`.
r := sar(174, p)
}
}
/// @dev Returns `W_0(x)`, denominated in `WAD`.
/// See: https://en.wikipedia.org/wiki/Lambert_W_function
/// a.k.a. Product log function. This is an approximation of the principal branch.
/// Note: This function is an approximation. Monotonically increasing.
function lambertW0Wad(int256 x) internal pure returns (int256 w) {
// forgefmt: disable-next-item
unchecked {
if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
int256 wad = int256(WAD);
int256 p = x;
uint256 c; // Whether we need to avoid catastrophic cancellation.
uint256 i = 4; // Number of iterations.
if (w <= 0x1ffffffffffff) {
if (-0x4000000000000 <= w) {
i = 1; // Inputs near zero only take one step to converge.
} else if (w <= -0x3ffffffffffffff) {
i = 32; // Inputs near `-1/e` take very long to converge.
}
} else if (uint256(w >> 63) == uint256(0)) {
/// @solidity memory-safe-assembly
assembly {
// Inline log2 for more performance, since the range is small.
let v := shr(49, w)
let l := shl(3, lt(0xff, v))
l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
c := gt(l, 60)
i := add(2, add(gt(l, 53), c))
}
} else {
int256 ll = lnWad(w = lnWad(w));
/// @solidity memory-safe-assembly
assembly {
// `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
i := add(3, iszero(shr(68, x)))
c := iszero(shr(143, x))
}
if (c == uint256(0)) {
do { // If `x` is big, use Newton's so that intermediate values won't overflow.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := mul(w, div(e, wad))
w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
}
if (p <= w) break;
p = w;
} while (--i != uint256(0));
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
return w;
}
}
do { // Otherwise, use Halley's for faster convergence.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := add(w, wad)
let s := sub(mul(w, e), mul(x, wad))
w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
}
if (p <= w) break;
p = w;
} while (--i != c);
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
// For certain ranges of `x`, we'll use the quadratic-rate recursive formula of
// R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation.
if (c != uint256(0)) {
int256 t = w | 1;
/// @solidity memory-safe-assembly
assembly {
x := sdiv(mul(x, wad), t)
}
x = (t * (wad + lnWad(x)));
/// @solidity memory-safe-assembly
assembly {
w := sdiv(x, add(wad, t))
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GENERAL NUMBER UTILITIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// 512-bit multiply `[p1 p0] = x * y`.
// Compute the product mod `2**256` and mod `2**256 - 1`
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that `product = p1 * 2**256 + p0`.
// Temporarily use `result` as `p0` to save gas.
result := mul(x, y) // Lower 256 bits of `x * y`.
for {} 1 {} {
// If overflows.
if iszero(mul(or(iszero(x), eq(div(result, x), y)), d)) {
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(result, lt(mm, result))) // Upper 256 bits of `x * y`.
/*------------------- 512 by 256 division --------------------*/
// Make division exact by subtracting the remainder from `[p1 p0]`.
let r := mulmod(x, y, d) // Compute remainder using mulmod.
let t := and(d, sub(0, d)) // The least significant bit of `d`. `t >= 1`.
// Make sure the result is less than `2**256`. Also prevents `d == 0`.
// Placing the check here seems to give more optimal stack operations.
if iszero(gt(d, p1)) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
d := div(d, t) // Divide `d` by `t`, which is a power of two.
// Invert `d mod 2**256`
// Now that `d` is an odd number, it has an inverse
// modulo `2**256` such that `d * inv = 1 mod 2**256`.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, `d * inv = 1 mod 2**4`.
let inv := xor(2, mul(3, d))
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
result :=
mul(
// Divide [p1 p0] by the factors of two.
// Shift in bits from `p1` into `p0`. For this we need
// to flip `t` such that it is `2**256 / t`.
or(
mul(sub(p1, gt(r, result)), add(div(sub(0, t), t), 1)),
div(sub(result, r), t)
),
mul(sub(2, mul(d, inv)), inv) // inverse mod 2**256
)
break
}
result := div(result, d)
break
}
}
}
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Behavior is undefined if `d` is zero or the final result cannot fit in 256 bits.
/// Performs the full 512 bit calculation regardless.
function fullMulDivUnchecked(uint256 x, uint256 y, uint256 d)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
result := mul(x, y)
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(result, lt(mm, result)))
let t := and(d, sub(0, d))
let r := mulmod(x, y, d)
d := div(d, t)
let inv := xor(2, mul(3, d))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
result :=
mul(
or(mul(sub(p1, gt(r, result)), add(div(sub(0, t), t), 1)), div(sub(result, r), t)),
mul(sub(2, mul(d, inv)), inv)
)
}
}
/// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Uniswap-v3-core under MIT license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
result = fullMulDiv(x, y, d);
/// @solidity memory-safe-assembly
assembly {
if mulmod(x, y, d) {
result := add(result, 1)
if iszero(result) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := div(z, d)
}
}
/// @dev Returns `ceil(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(z, d))), div(z, d))
}
}
/// @dev Returns `ceil(x / d)`.
/// Reverts if `d` is zero.
function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
if iszero(d) {
mstore(0x00, 0x65244e4e) // `DivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(x, d))), div(x, d))
}
}
/// @dev Returns `max(0, x - y)`.
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`.
/// Reverts if the computation overflows.
function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`.
if x {
z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x`
let half := shr(1, b) // Divide `b` by 2.
// Divide `y` by 2 every iteration.
for { y := shr(1, y) } y { y := shr(1, y) } {
let xx := mul(x, x) // Store x squared.
let xxRound := add(xx, half) // Round to the nearest number.
// Revert if `xx + half` overflowed, or if `x ** 2` overflows.
if or(lt(xxRound, xx), shr(128, x)) {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
x := div(xxRound, b) // Set `x` to scaled `xxRound`.
// If `y` is odd:
if and(y, 1) {
let zx := mul(z, x) // Compute `z * x`.
let zxRound := add(zx, half) // Round to the nearest number.
// If `z * x` overflowed or `zx + half` overflowed:
if or(xor(div(zx, x), z), lt(zxRound, zx)) {
// Revert if `x` is non-zero.
if x {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
}
z := div(zxRound, b) // Return properly scaled `zxRound`.
}
}
}
}
}
/// @dev Returns the square root of `x`, rounded down.
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// Let `y = x / 2**r`. We check `y >= 2**(k + 8)`
// but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffffff, shr(r, x))))
z := shl(shr(1, r), z)
// Goal was to get `z*z*y` within a small factor of `x`. More iterations could
// get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
// We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
// That's not possible if `x < 256` but we can just verify those cases exhaustively.
// Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
// Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
// Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.
// For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
// is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
// with largest error when `s = 1` and when `s = 256` or `1/256`.
// Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
// Then we can estimate `sqrt(y)` using
// `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.
// There is no overflow risk here since `y < 2**136` after the first branch above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If `x+1` is a perfect square, the Babylonian method cycles between
// `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
z := sub(z, lt(div(x, z), z))
}
}
/// @dev Returns the cube root of `x`, rounded down.
/// Credit to bout3fiddy and pcaversaccio under AGPLv3 license:
/// https://github.com/pcaversaccio/snekmate/blob/main/src/utils/Math.vy
function cbrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3)))
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := sub(z, lt(div(x, mul(z, z)), z))
}
}
/// @dev Returns the square root of `x`, denominated in `WAD`, rounded down.
function sqrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 18) return sqrt(x * 10 ** 18);
z = (1 + sqrt(x)) * 10 ** 9;
z = (fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1;
}
/// @solidity memory-safe-assembly
assembly {
z := sub(z, gt(999999999999999999, sub(mulmod(z, z, x), 1)))
}
}
/// @dev Returns the cube root of `x`, denominated in `WAD`, rounded down.
function cbrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 36) return cbrt(x * 10 ** 36);
z = (1 + cbrt(x)) * 10 ** 12;
z = (fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3;
}
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(sub(exp(10, 36), 2), sub(mulmod(mul(z, z), z, x), 1))) {
// forgefmt: disable-next-item
z := sub(z, eq(mulmod(mul(z, z), z, sub(x, 1)),
add(exp(10, 36), mulmod(mul(z, z), z, x))))
}
}
}
/// @dev Returns the factorial of `x`.
function factorial(uint256 x) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 58)) {
mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`.
revert(0x1c, 0x04)
}
for { result := 1 } x { x := sub(x, 1) } { result := mul(result, x) }
}
}
/// @dev Returns the log2 of `x`.
/// Equivalent to computing the index of the most significant bit (MSB) of `x`.
/// Returns 0 if `x` is zero.
function log2(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Returns the log2 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log2Up(uint256 x) internal pure returns (uint256 r) {
r = log2(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(r, 1), x))
}
}
/// @dev Returns the log10 of `x`.
/// Returns 0 if `x` is zero.
function log10(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 100000000000000000000000000000000000000)) {
x := div(x, 100000000000000000000000000000000000000)
r := 38
}
if iszero(lt(x, 100000000000000000000)) {
x := div(x, 100000000000000000000)
r := add(r, 20)
}
if iszero(lt(x, 10000000000)) {
x := div(x, 10000000000)
r := add(r, 10)
}
if iszero(lt(x, 100000)) {
x := div(x, 100000)
r := add(r, 5)
}
r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999)))))
}
}
/// @dev Returns the log10 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log10Up(uint256 x) internal pure returns (uint256 r) {
r = log10(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(exp(10, r), x))
}
}
/// @dev Returns the log256 of `x`.
/// Returns 0 if `x` is zero.
function log256(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(shr(3, r), lt(0xff, shr(r, x)))
}
}
/// @dev Returns the log256 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log256Up(uint256 x) internal pure returns (uint256 r) {
r = log256(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(shl(3, r), 1), x))
}
}
/// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`.
/// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent).
function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) {
/// @solidity memory-safe-assembly
assembly {
mantissa := x
if mantissa {
if iszero(mod(mantissa, 1000000000000000000000000000000000)) {
mantissa := div(mantissa, 1000000000000000000000000000000000)
exponent := 33
}
if iszero(mod(mantissa, 10000000000000000000)) {
mantissa := div(mantissa, 10000000000000000000)
exponent := add(exponent, 19)
}
if iszero(mod(mantissa, 1000000000000)) {
mantissa := div(mantissa, 1000000000000)
exponent := add(exponent, 12)
}
if iszero(mod(mantissa, 1000000)) {
mantissa := div(mantissa, 1000000)
exponent := add(exponent, 6)
}
if iszero(mod(mantissa, 10000)) {
mantissa := div(mantissa, 10000)
exponent := add(exponent, 4)
}
if iszero(mod(mantissa, 100)) {
mantissa := div(mantissa, 100)
exponent := add(exponent, 2)
}
if iszero(mod(mantissa, 10)) {
mantissa := div(mantissa, 10)
exponent := add(exponent, 1)
}
}
}
}
/// @dev Convenience function for packing `x` into a smaller number using `sci`.
/// The `mantissa` will be in bits [7..255] (the upper 249 bits).
/// The `exponent` will be in bits [0..6] (the lower 7 bits).
/// Use `SafeCastLib` to safely ensure that the `packed` number is small
/// enough to fit in the desired unsigned integer type:
/// ```
/// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether));
/// ```
function packSci(uint256 x) internal pure returns (uint256 packed) {
(x, packed) = sci(x); // Reuse for `mantissa` and `exponent`.
/// @solidity memory-safe-assembly
assembly {
if shr(249, x) {
mstore(0x00, 0xce30380c) // `MantissaOverflow()`.
revert(0x1c, 0x04)
}
packed := or(shl(7, x), packed)
}
}
/// @dev Convenience function for unpacking a packed number from `packSci`.
function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) {
unchecked {
unpacked = (packed >> 7) * 10 ** (packed & 0x7f);
}
}
/// @dev Returns the average of `x` and `y`.
function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = (x & y) + ((x ^ y) >> 1);
}
}
/// @dev Returns the average of `x` and `y`.
function avg(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = (x >> 1) + (y >> 1) + (x & y & 1);
}
}
/// @dev Returns the absolute value of `x`.
function abs(int256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(sar(255, x), add(sar(255, x), x))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(mul(xor(sub(y, x), sub(x, y)), gt(x, y)), sub(y, x))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(int256 x, int256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(mul(xor(sub(y, x), sub(x, y)), sgt(x, y)), sub(y, x))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), slt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), sgt(y, x)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(uint256 x, uint256 minValue, uint256 maxValue)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), gt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), lt(maxValue, z)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), sgt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), slt(maxValue, z)))
}
}
/// @dev Returns greatest common divisor of `x` and `y`.
function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
for { z := x } y {} {
let t := y
y := mod(z, y)
z := t
}
}
}
/// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`,
/// with `t` clamped between `begin` and `end` (inclusive).
/// Agnostic to the order of (`a`, `b`) and (`end`, `begin`).
/// Reverts if `begin` equals `end` (due to division by zero).
function lerp(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end)
internal
pure
returns (uint256)
{
if (begin >= end) {
t = ~t;
begin = ~begin;
end = ~end;
}
if (t <= begin) return a;
if (t >= end) return b;
unchecked {
if (b >= a) return a + fullMulDiv(b - a, t - begin, end - begin);
return a - fullMulDiv(a - b, t - begin, end - begin);
}
}
/// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`.
/// with `t` clamped between `begin` and `end` (inclusive).
/// Agnostic to the order of (`a`, `b`) and (`end`, `begin`).
/// Reverts if `begin` equals `end` (due to division by zero).
function lerp(int256 a, int256 b, int256 t, int256 begin, int256 end)
internal
pure
returns (int256)
{
if (begin >= end) {
t = int256(~uint256(t));
begin = int256(~uint256(begin));
end = int256(~uint256(end));
}
if (t <= begin) return a;
if (t >= end) return b;
// forgefmt: disable-next-item
unchecked {
if (b >= a) return int256(uint256(a) + fullMulDiv(uint256(b) - uint256(a),
uint256(t) - uint256(begin), uint256(end) - uint256(begin)));
return int256(uint256(a) - fullMulDiv(uint256(a) - uint256(b),
uint256(t) - uint256(begin), uint256(end) - uint256(begin)));
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RAW NUMBER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(x, y)
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mod(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := smod(x, y)
}
}
/// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := addmod(x, y, d)
}
}
/// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mulmod(x, y, d)
}
}
}{
"viaIR": true,
"optimizer": {
"enabled": true,
"runs": 888888
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_gameConfigurationManager","type":"address"},{"internalType":"address","name":"_transferManager","type":"address"},{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"_vrfCoordinator","type":"address"},{"internalType":"address","name":"_blast","type":"address"},{"internalType":"address","name":"_usdb","type":"address"},{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_blastPoints","type":"address"},{"internalType":"address","name":"_blastPointsOperator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ERC20TransferFail","type":"error"},{"inputs":[],"name":"Game__InexactNativeTokensSupplied","type":"error"},{"inputs":[],"name":"Game__InvalidMultiplier","type":"error"},{"inputs":[],"name":"Game__InvalidStops","type":"error"},{"inputs":[],"name":"Game__InvalidValue","type":"error"},{"inputs":[],"name":"Game__LiquidityPoolConnected","type":"error"},{"inputs":[],"name":"Game__LiquidityPoolPaused","type":"error"},{"inputs":[],"name":"Game__NoLiquidityPool","type":"error"},{"inputs":[],"name":"Game__NoOngoingRound","type":"error"},{"inputs":[],"name":"Game__NoPendingRandomnessRequest","type":"error"},{"inputs":[],"name":"Game__OngoingRound","type":"error"},{"inputs":[],"name":"Game__PlayAmountPerRoundTooHigh","type":"error"},{"inputs":[],"name":"Game__PlayAmountPerRoundTooLow","type":"error"},{"inputs":[],"name":"Game__TooEarlyForARefund","type":"error"},{"inputs":[],"name":"Game__TooManyRounds","type":"error"},{"inputs":[],"name":"Game__WrongVrfCoordinator","type":"error"},{"inputs":[],"name":"Game__ZeroKellyFraction","type":"error"},{"inputs":[],"name":"Game__ZeroMultiplier","type":"error"},{"inputs":[],"name":"Game__ZeroNumberOfRounds","type":"error"},{"inputs":[],"name":"Game__ZeroPlayAmountPerRound","type":"error"},{"inputs":[],"name":"NoOngoingTransferInProgress","type":"error"},{"inputs":[],"name":"NotAContract","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"want","type":"address"}],"name":"OnlyCoordinatorCanFulfill","type":"error"},{"inputs":[],"name":"ReentrancyFail","type":"error"},{"inputs":[],"name":"RenouncementNotInProgress","type":"error"},{"inputs":[],"name":"TransferAlreadyInProgress","type":"error"},{"inputs":[],"name":"TransferNotInProgress","type":"error"},{"inputs":[],"name":"WrongPotentialOwner","type":"error"},{"anonymous":false,"inputs":[],"name":"CancelOwnershipTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"blockNumber","type":"uint256"},{"indexed":false,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalPlayAmount","type":"uint256"}],"name":"Game__Refunded","type":"event"},{"anonymous":false,"inputs":[],"name":"InitiateOwnershipRenouncement","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":false,"internalType":"address","name":"potentialOwner","type":"address"}],"name":"InitiateOwnershipTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"blockNumber","type":"uint256"},{"indexed":false,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"results","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"payouts","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"numberOfRoundsPlayed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"protocolFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidityPoolFee","type":"uint256"}],"name":"Quantum__GameCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"blockNumber","type":"uint256"},{"indexed":false,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"numberOfRounds","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"playAmountPerRound","type":"uint256"},{"indexed":false,"internalType":"address","name":"currency","type":"address"},{"indexed":false,"internalType":"int256","name":"stopGain","type":"int256"},{"indexed":false,"internalType":"int256","name":"stopLoss","type":"int256"},{"indexed":false,"internalType":"bool","name":"isAbove","type":"bool"},{"indexed":false,"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"Quantum__GameCreated","type":"event"},{"inputs":[],"name":"GAME_CONFIGURATION_MANAGER","outputs":[{"internalType":"contract IGameConfigurationManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRANSFER_MANAGER","outputs":[{"internalType":"contract ITransferManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"calculateWinProbability","outputs":[{"internalType":"uint256","name":"winProbability","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"cancelOwnershipTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"claimYield","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"confirmOwnershipRenouncement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"confirmOwnershipTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"winProbability","type":"uint256"}],"name":"defineBoundary","outputs":[{"internalType":"uint256","name":"boundary","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"games","outputs":[{"components":[{"internalType":"uint40","name":"blockNumber","type":"uint40"},{"internalType":"uint40","name":"randomnessRequestedAt","type":"uint40"},{"internalType":"uint16","name":"numberOfRounds","type":"uint16"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint256","name":"playAmountPerRound","type":"uint256"},{"internalType":"int256","name":"stopGain","type":"int256"},{"internalType":"int256","name":"stopLoss","type":"int256"},{"internalType":"uint256","name":"vrfFee","type":"uint256"},{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256","name":"protocolFeeBasisPoints","type":"uint256"},{"internalType":"uint256","name":"liquidityPoolFeeBasisPoints","type":"uint256"}],"internalType":"struct Game.Game__GameParams","name":"params","type":"tuple"},{"internalType":"bool","name":"isAbove","type":"bool"},{"internalType":"uint248","name":"multiplier","type":"uint248"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initiateOwnershipRenouncement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newPotentialOwner","type":"address"}],"name":"initiateOwnershipTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"kellyFraction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"maxPlayAmountPerGame","outputs":[{"internalType":"uint256","name":"maxPlayAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"minPlayAmountPerGame","outputs":[{"internalType":"uint256","name":"minPlayAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ownershipStatus","outputs":[{"internalType":"enum IOwnableTwoSteps.Status","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"numberOfRounds","type":"uint16"},{"internalType":"uint256","name":"playAmountPerRound","type":"uint256"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"int256","name":"stopGain","type":"int256"},{"internalType":"int256","name":"stopLoss","type":"int256"},{"internalType":"bool","name":"isAbove","type":"bool"},{"internalType":"uint248","name":"multiplier","type":"uint248"}],"name":"play","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"potentialOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"randomnessRequests","outputs":[{"internalType":"address","name":"requester","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refund","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
610120604081815234620004045760006101208362003b9a803803809162000028828562000433565b833981010312620002e7576200003e8362000457565b6020906200004e85830162000457565b6200005b86860162000457565b620000696060880162000457565b9686620000796080830162000457565b6200008760a0840162000457565b9460c09a8b8501620000999062000457565b95620000a860e0870162000457565b9561010001620000b89062000457565b6001808d55608084905280546001600160a01b0319166001600160a01b03998a169081179091559551868152979990978a9182917f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc908e90a116938460a052168d5260e05286610100528b8b51809363a5d37e8d60e01b8252815a91600492fa918215620003fa579088918b9362000363575b5081169116036200035257851690813b156200034e578791606483928b51948593849263c8992e6160e01b8452600260048501526001602485015260448401525af1801562000344576200032e575b50831690813b156200032a578360248792838a5195869485936336b91f2b60e01b85521660048401525af180156200032057918593918593620002fc575b508651631a33757d60e01b815260026004820152938492602492849291165af18015620002f257620002c0575b5050505161372d91826200046d833960805182611788015260a05182818161076101528181610b4c0152818161131501528181611d5d01528181611fbb0152818161263e0152818161292001528181612c2301528181612f600152818161302e0152818161311a015281816131bb015281816132bf01526136130152518181816117110152612e5c015260e0518181816107fc01528181610896015281816120aa015281816132f901526136c9015261010051816110910152f35b813d8311620002ea575b620002d6818362000433565b81010312620002e757808062000205565b80fd5b503d620002ca565b84513d85823e3d90fd5b6200030c91935093919362000409565b6200031c578391839138620001d8565b8380fd5b86513d87823e3d90fd5b8580fd5b956200033c85929762000409565b95906200019a565b88513d89823e3d90fd5b8780fd5b885163240e9d9560e01b8152600490fd5b8d809294508193503d8311620003f2575b62000380818362000433565b81010312620003ea57620003948162000457565b898201519091906001600160401b03811603620003ee578b81015163ffffffff811603620003ee57606081015161ffff811603620003ee57608001516001600160f01b03811603620003ea57908790816200014b565b8980fd5b8a80fd5b503d62000374565b8b513d8c823e3d90fd5b600080fd5b6001600160401b0381116200041d57604052565b634e487b7160e01b600052604160045260246000fd5b601f909101601f19168101906001600160401b038211908210176200041d57604052565b51906001600160a01b0382168203620004045756fe6080604052600436101561001257600080fd5b60003560e01c80631fe543e31461016757806323452b9c146101625780632bb5a9e61461015d5780633bf7696e146101585780633e567539146101535780635452d9b11461014e578063590e1ae3146101495780635b6ac011146101445780635cb6dfff1461013f5780636f64c4c41461013a57806370799963146101355780637200b829146101305780637762df251461012b57806379131a19146101265780638da5cb5b146101215780639877fbf01461011c578063999927df146101175780639a86256514610112578063c0b6f5611461010d578063f01f7ce4146101085763ffefdd0f1461010357600080fd5b611735565b6116c6565b611592565b61118f565b61100a565b610fce565b610f7c565b610e5c565b610d29565b610bb9565b610b70565b610b01565b610aa1565b610998565b6106af565b610673565b610541565b6104e8565b61047f565b61033b565b61027e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176101b757604052565b61016c565b67ffffffffffffffff81116101b757604052565b610160810190811067ffffffffffffffff8211176101b757604052565b6060810190811067ffffffffffffffff8211176101b757604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176101b757604052565b60405190610257826101d0565b565b60405190610257826101ed565b67ffffffffffffffff81116101b75760051b60200190565b3461032b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5760243567ffffffffffffffff811161032b573660238201121561032b578060040135906102d982610266565b906102e76040519283610209565b8282526020926024602084019160051b8301019136831161032b57602401905b82821061031c5761031a84600435611771565b005b81358152908401908401610307565b600080fd5b600091031261032b57565b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261044357610373612582565b60025460ff8160a01c1661038681610446565b80156104195780610398600192610446565b146103ee575b507fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff600254166002557f8eca980489e87f7dba4f26917aa4bfc906eb3f2b4f7b4b9fd0ff2b8bb3e21ae38180a180f35b7fffffffffffffffffffffffff0000000000000000000000000000000000000000166002553861039e565b60046040517fccf69db7000000000000000000000000000000000000000000000000000000008152fd5b80fd5b6003111561045057565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5760ff60025460a01c166040516003821015610450576020918152f35b73ffffffffffffffffffffffffffffffffffffffff81160361032b57565b3461032b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576020610539600435610528816104ca565b61053460243582611e24565b6125cd565b604051908152f35b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261044357610579612582565b60ff60025460a01c1660038110156106465760020361061c576105bf7fffffffffffffffffffffffff000000000000000000000000000000000000000060015416600155565b6105ec7fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b604051600081527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc90602090a180f35b60046040517f045c5122000000000000000000000000000000000000000000000000000000008152fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576020610539600435611cea565b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261044357600281541461096e576002815533815260049081602052604080822090815461ffff8160501c169081156109465764ffffffffff808260281c16801561091e578451907f6fac17110000000000000000000000000000000000000000000000000000000082526020828a8173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156109195761079b9289926108e8575b506127fc565b429116116108c057946107d87f4716cc7ed643b171db3dc2065ce09672ebba9964fe933779d9f862ae75934cba959660601c926001860154611cd7565b918061086957506107f36108509261082b92860154906124c2565b936108205a86337f000000000000000000000000000000000000000000000000000000000000000061349a565b5464ffffffffff1690565b915164ffffffffff909216825233602083015260408201929092529081906060820190565b0390a161085c33612815565b6108666001600055565b80f35b9361082b9161087e846108509597339061339c565b8101548061088d575b50610820565b6108ba905a90337f000000000000000000000000000000000000000000000000000000000000000061349a565b38610887565b8583517fc3f53662000000000000000000000000000000000000000000000000000000008152fd5b61090b91925060203d602011610912575b6109038183610209565b8101906127df565b9038610795565b503d6108f9565b611e18565b8785517f79ed4ed0000000000000000000000000000000000000000000000000000000008152fd5b8583517f0a6499c8000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1bbee726000000000000000000000000000000000000000000000000000000008152fd5b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610443576109d0612582565b60025460ff8160a01c166003811015610a7457610a4a577fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674020000000000000000000000000000000000000000176002557f3ff05a45e46337fa1cbf20996d2eeb927280bce099f37252bcca1040609604ec8180a180f35b60046040517f74ed79ae000000000000000000000000000000000000000000000000000000008152fd5b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576004356000526003602052602073ffffffffffffffffffffffffffffffffffffffff60406000205416604051908152f35b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b3461032b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576020610539600435610bb0816104ca565b60243590611e24565b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57600254600160ff8260a01c16610bfd81610446565b03610cff5773ffffffffffffffffffffffffffffffffffffffff163303610cd557600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001633179055610c757fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b610ca27fffffffffffffffffffffffff000000000000000000000000000000000000000060025416600255565b6040513381527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc9080602081015b0390a1005b60046040517fafdcfb92000000000000000000000000000000000000000000000000000000008152fd5b60046040517f5e4f2826000000000000000000000000000000000000000000000000000000008152fd5b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b61018090610e3561025794969593966101a0830197610da184825164ffffffffff169052565b60208181015164ffffffffff169085015260408181015161ffff169085015260608181015173ffffffffffffffffffffffffffffffffffffffff16908501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e085015261010080820151908501526101208082015190850152610140809101519084015261016083019015159052565b01907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5773ffffffffffffffffffffffffffffffffffffffff600435610eac816104ca565b16600052600460205260406000206008610ec461024a565b91610f148154610efb64ffffffffff610ee6818416889064ffffffffff169052565b8260281c16602087019064ffffffffff169052565b605081901c61ffff166040860152606090811c90850152565b60018101546080840152600281015460a0840152600381015460c0840152600481015460e0840152600581015461010084015260068101546101208401526007810154610140840152015490610f7860405192839260ff8260081c92169084610d7b565b0390f35b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5760206105396004356120ce565b3461032b576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57600435611046816104ca565b61104e612582565b6040517fe12f3a6100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001691908381602481865afa9081156109195760009161115c575b50806110d257005b6040517faad3ec9600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9290921660048301526024820152908290829060449082906000905af180156109195761113657005b8161031a92903d10611155575b61114d8183610209565b810190611e09565b503d611143565b6111739150843d86116111555761114d8183610209565b386110ca565b61ffff81160361032b57565b8015150361032b57565b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576004356111c581611179565b602435604435916111d5836104ca565b6064359260843560a4356111e881611185565b60c435917effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83169687840361032b5760026000541461096e57600260005561ffff9485871698611237898b6128d7565b61128161127c61127561126a3373ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b5460501c61ffff1690565b61ffff1690565b612a0f565b61128b8484612a3f565b611294816126ac565b6112a86112a18b8b611cd7565b9183611e24565b80821161156857826112b9916125cd565b1161153e5787956112c982612a9b565b6112dd6112d4612bdf565b98819c85612de8565b604080517fdd8bd8540000000000000000000000000000000000000000000000000000000081523060048201529790919082896024817f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff165afa918215610919578a8161147d947f030123afc603cda1c31a8f75478e406e4bcd6cb42996fc50b4e2fea4c8c834249f948f956115029e60009361150f575b5064ffffffffff966113e56113b060206113a6875161ffff1690565b96015161ffff1690565b966113db6113bc61024a565b64ffffffffff438d161681529a421660208c019064ffffffffff169052565b61ffff16898c0152565b73ffffffffffffffffffffffffffffffffffffffff8b16606089015260808801528a60a08801528b60c088015260e08701526101008601521661012084015216610140820152611433610259565b90815286151560208201527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff881681840152336000908152600460205260409020612152565b612152565b5197889733438a9793947effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9692919a999561ffff61010099956101208c019d8c5273ffffffffffffffffffffffffffffffffffffffff80951660208d01521660408b015260608a015216608088015260a087015260c0860152151560e085015216910152565b0390a161031a6001600055565b611530919350893d8b11611537575b6115288183610209565b810190612118565b913861138a565b503d61151e565b60046040517f367b6990000000000000000000000000000000000000000000000000000000008152fd5b60046040517ffb167496000000000000000000000000000000000000000000000000000000008152fd5b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576004356115cd816104ca565b6115d5612582565b60ff60025460a01c16600381101561045057610a4a57610cd07fb86c75c9bffca616b2d314cc914f7c3f1d174255b16b941c3f3ededee276d5ef91611654740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6002541617600255565b6116998173ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006002541617600255565b6040805133815273ffffffffffffffffffffffffffffffffffffffff909216602083015290918291820190565b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602061053960043561270b565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000008181163303611c03575060026000541461096e5760026000556117f16117d7836000526003602052604060002090565b5473ffffffffffffffffffffffffffffffffffffffff1690565b908116611806575b5050506102576001600055565b6118308173ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b91825461183f8160601c612f0f565b9081611be9575b5080611bdc575b611858575b506117f9565b61187361189b91959394956000526003602052604060002090565b7fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b6118a3612385565b936118ce6118ba611275865461ffff9060501c1690565b916118c4836123c4565b6080880152612413565b5160208601526118dd816123c4565b906118e6612463565b936002860154936003870154946008880154955b895185811015611b9f5761191360408c0151838561306f565b611b175761193a61193360208d01516a084595161401484a000000900690565b918861244f565b5289878a60ff821680611aef575b8015611ac4575b15611aa5576119cc926040611a23611a1a61199a946119d1611a2d9760081c6001830154998a936119cc6119c46119bc6119a461199a61198f8a89611cd7565b600687015490611cd7565b6305f5e100900490565b9c8d9460076119b38b8a611cd7565b91015490611cd7565b9d8e95611cd7565b612710900490565b611cb3565b6119e1608087015187519061244f565b526119f2608086015186519061244f565b51611a02606087019182516124c2565b9052611a14608086015186519061244f565b516124a9565b828401516124cf565b9101528a516124c2565b8952611a3e60208a019182516124c2565b90525b60208a0151604051611a8c81611a606020820194859190602083019252565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610209565b51902060208b0152611a9e8a5161247c565b8a526118fa565b611aba925060019150015460408c01516124a9565b60408b0152611a41565b5060ff821615801561194f5750611adc83518a61244f565b51611ae98360081c61270b565b1161194f565b50611afb83518a61244f565b51611b10611b0b8460081c61270b565b6120ce565b1115611948565b505050947fa4e48c2cf5e4a2bdcbde67ad67bb430348ef15d3451d2f546ce2d7f522e129ad9450611b8e91979250928593611b71611b96985b611b6486516060880151855191848b6130a9565b6108206004820154613273565b93608081015190519060208351930151936040519788978861251f565b0390a1612815565b38808080611852565b505050947fa4e48c2cf5e4a2bdcbde67ad67bb430348ef15d3451d2f546ce2d7f522e129ad9450611b8e91979250928593611b71611b9698611b50565b506005830154811461184d565b611bfd915060281c64ffffffffff16612fb8565b38611846565b6040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff919091166024820152604490fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b907ffffffffffffffffffffffffffffffffffffffffffff7ba6ae9ebfeb7b60000008201918211611cae57565b611c52565b91908203918211611cae57565b906103e891828102928184041490151715611cae57565b81810292918115918404141715611cae57565b611cf3816126ac565b611cfc8161270b565b6a084595161401484a00000091818303928311611cae57604051927fdd8bd85400000000000000000000000000000000000000000000000000000000845230600485015260408460248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa90811561091957611dd5611dd0611dcb611dc6611de196611dc0611275611dbb6020611ddb9a611de79e600091611dea575b50015161ffff1690565b6126f7565b90611cd7565b611cc0565b612731565b611c81565b906127aa565b91612769565b90611cb3565b90565b611e03915060403d604011611537576115288183610209565b38611db1565b9081602091031261032b575190565b6040513d6000823e3d90fd5b611e2d826126ac565b8091600090611e3b836135c0565b9373ffffffffffffffffffffffffffffffffffffffff809416156120a7575b604051947f3e041d0f00000000000000000000000000000000000000000000000000000000865260209586816004818986165afa90811561091957859161207a575b506040517ff3f4370300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152908790829060249082908a165afa92831561091957869288928795612055575b506040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909116600482015292839190829081602481015b0392165afa90811561091957611f809491612038575b5081811061202b57611dc091611f7a91611cb3565b91611cea565b6040517faf49f220000000000000000000000000000000000000000000000000000000008152306004820152909290918190839060249082907f0000000000000000000000000000000000000000000000000000000000000000165afa801561091957611de7936119c493611ffe9360009361200c575b5050611cd7565b670de0b6b3a7640000900490565b612023929350803d106111555761114d8183610209565b903880611ff7565b5050611dc0600091611cea565b61204f9150863d88116111555761114d8183610209565b38611f65565b611f4f9291955061207290843d86116111555761114d8183610209565b949091611eff565b61209a9150873d89116120a0575b6120928183610209565b8101906128ad565b38611e9c565b503d612088565b507f0000000000000000000000000000000000000000000000000000000000000000611e5a565b6a084595161401484a000000908181116120ee578103908111611cae5790565b60046040517f8b88ffad000000000000000000000000000000000000000000000000000000008152fd5b9081604091031261032b576020604051916121328361019b565b805161213d81611179565b8352015161214a81611179565b602082015290565b90612354604060086102579461014085516121a7612175825164ffffffffff1690565b849064ffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000825416179055565b6121f76121bc602083015164ffffffffff1690565b84547fffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffffff1660289190911b69ffffffffff000000000016178455565b6122456122088683015161ffff1690565b84547fffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffff1660509190911b6bffff0000000000000000000016178455565b6122a6612269606083015173ffffffffffffffffffffffffffffffffffffffff1690565b84546bffffffffffffffffffffffff1660609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016178455565b6080810151600184015560a0810151600284015560c0810151600384015560e081015160048401556101008101516005840155610120810151600684015501516007820155019261232e6122fd6020830151151590565b859060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b01517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b6040519060a0820182811067ffffffffffffffff8211176101b75760405260606080836000815260006020820152600060408201526000838201520152565b906123ce82610266565b6123db6040519182610209565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06124098294610266565b0190602036910137565b8051156124205760200190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b80518210156124205760209160051b010190565b604051906124708261019b565b60006020838281520152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611cae5760010190565b81810392916000138015828513169184121617611cae57565b91908201809211611cae57565b91909160008382019384129112908015821691151617611cae57565b90815180825260208080930193019160005b82811061250b575050505090565b8351855293810193928101926001016124fd565b939060c0959897969373ffffffffffffffffffffffffffffffffffffffff6125739464ffffffffff6125659416885216602087015260e0604087015260e08601906124eb565b9084820360608601526124eb565b95608083015260a08201520152565b73ffffffffffffffffffffffffffffffffffffffff6001541633036125a357565b60046040517f30cd7471000000000000000000000000000000000000000000000000000000008152fd5b6040517f91c4508b00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff9092166024830152612710900491906020818060448101038173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156109195760009161268d575b5082811080612684575b6126805750565b9150565b50801515612679565b6126a6915060203d6020116111555761114d8183610209565b3861266f565b61291e81109081156126ea575b506126c057565b60046040517fa0a21059000000000000000000000000000000000000000000000000000000008152fd5b62989680915011386126b9565b9061ffff80921661271003918211611cae57565b8015612723576c01431e0fae6d7217caa00000000490565b637c5f487d6000526004601cfd5b670de0b6b3a7640000612710917812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21811182021583021561272357020490565b670de0b6b3a76400006a084595161401484a000000917812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21811182021583021561272357020490565b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21811182021583021561272357020490565b9081602091031261032b575164ffffffffff8116810361032b5790565b91909164ffffffffff80809416911601918211611cae57565b6102579061147860405191612829836101d0565b60008084528060208501528060408501528060608501528060808501528060a08501528060c08501528060e08501528061010085015280610120850152806101408501526040519361287a856101ed565b8452806020850152604084015273ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b9081602091031261032b5751611de7816104ca565b9081602091031261032b5751611de781611179565b80156129e5576040517f6a50887100000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa80156109195761ffff916000916129b6575b50161061298c571561296257565b60046040517fcd57ebca000000000000000000000000000000000000000000000000000000008152fd5b60046040517f7e0307aa000000000000000000000000000000000000000000000000000000008152fd5b6129d8915060203d6020116129de575b6129d08183610209565b8101906128c2565b38612954565b503d6129c6565b60046040517f5ced2d51000000000000000000000000000000000000000000000000000000008152fd5b612a1557565b60046040517f9ad3d189000000000000000000000000000000000000000000000000000000008152fd5b600013908115612a7b575b50612a5157565b60046040517fb81df4da000000000000000000000000000000000000000000000000000000008152fd5b600091501338612a4a565b9081602091031261032b5751611de781611185565b602073ffffffffffffffffffffffffffffffffffffffff612abd6004936135c0565b16604051928380927f5c975abb0000000000000000000000000000000000000000000000000000000082525afa90811561091957600091612b2a575b50612b0057565b60046040517feaa69c55000000000000000000000000000000000000000000000000000000008152fd5b612b4c915060203d602011612b52575b612b448183610209565b810190612a86565b38612af9565b503d612b3a565b91908260c091031261032b578151612b70816104ca565b91602081015167ffffffffffffffff8116810361032b5791604082015163ffffffff8116810361032b57916060810151612ba981611179565b9160808201517dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116810361032b5760a09092015190565b73ffffffffffffffffffffffffffffffffffffffff6040517fa5d37e8d00000000000000000000000000000000000000000000000000000000815260c081600481857f0000000000000000000000000000000000000000000000000000000000000000165afa90811561091957600091828093819282948391612da5575b506020949596600091612cd2604051998a97889687947f5d3b1d300000000000000000000000000000000000000000000000000000000086526004860190949363ffffffff9061ffff60019567ffffffffffffffff60809660a087019a87521660208601521660408401521660608201520152565b0393165af191821561091957600092612d64575b507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90612d6083612d2033916000526003602052604060002090565b9073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b1691565b7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff919250612d9e9060203d6020116111555761114d8183610209565b9190612ce6565b925050506000935060209250612dd3915060c03d60c011612de1575b612dcb8183610209565b810190612b59565b929650949093929190612c5d565b503d612dc1565b91929173ffffffffffffffffffffffffffffffffffffffff808216612e4d575050612e1690612e1b93611cd7565b6124c2565b3403612e2357565b60046040517fe3057cf1000000000000000000000000000000000000000000000000000000008152fd5b9190923403612e2357612e82917f00000000000000000000000000000000000000000000000000000000000000001693611cd7565b823b1561032b576040517fda3e8ce400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921660048301523360248301523060448301526064820152906000908290818381608481015b03925af1801561091957612efc5750565b80612f09610257926101bc565b80610330565b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91821660248201526020816044817f000000000000000000000000000000000000000000000000000000000000000086165afa90811561091957600091612f99575b5016151590565b612fb2915060203d6020116120a0576120928183610209565b38612f92565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed464ffffffffff80921601818111611cae57604051907f6fac171100000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa90811561091957613068926000926108e857506127fc565b4291161190565b9190821515928361309e575b50821561308757505090565b8015159250908261309757505090565b1315905090565b82121592503861307b565b9093926130ef906130e66001870154946130e08854936130da6130d461ffff8760501c169485611cb3565b89611cd7565b906124c2565b95611cd7565b9060601c61369a565b816131a4575b505080613100575050565b61314273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016925460601c90565b90823b1561032b576040517f7c0efbd700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9092166004830152602482015290600090829081838160448101612eeb565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906131e7855460601c90565b91803b1561032b576040517f7d1d27f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff93841660048201526024810194909452911660448301526000908290606490829084905af1801561091957613260575b806130f5565b80612f0961326d926101bc565b3861325a565b8061327b5750565b604051907fcaf7564a00000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115610919576102579260009261331d575b505a917f000000000000000000000000000000000000000000000000000000000000000061349a565b61333791925060203d6020116120a0576120928183610209565b90386132f4565b3d15613397573d9067ffffffffffffffff82116101b7576040519161338b60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610209565b82523d6000602084013e565b606090565b919091803b15613470576040517fa9059cbb000000000000000000000000000000000000000000000000000000006020820190815273ffffffffffffffffffffffffffffffffffffffff909416602482015260448101929092526000928392839061340a8160648101611a60565b51925af161341661333e565b901561344657805180613427575050565b8160208061343c936134409501019101612a86565b1590565b61344657565b60046040517ff1568f95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f09ee12d5000000000000000000000000000000000000000000000000000000008152fd5b6134af82849395600080809781948294f11590565b6134ba575b50505050565b73ffffffffffffffffffffffffffffffffffffffff16803b156135bc57604051937fd0e30db0000000000000000000000000000000000000000000000000000000008552838560048186865af193841561091957613573956020956135a9575b506040518096819582947fa9059cbb000000000000000000000000000000000000000000000000000000008452600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af180156109195761358a575b8080806134b4565b6135a29060203d602011612b5257612b448183610209565b5038613582565b80612f096135b6926101bc565b3861351a565b8280fd5b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff918216602482015291906020836044817f000000000000000000000000000000000000000000000000000000000000000085165afa92831561091957600093613679575b5082161561364f57565b60046040517f4daebedc000000000000000000000000000000000000000000000000000000008152fd5b61369391935060203d6020116120a0576120928183610209565b9138613645565b906136a4826135c0565b9073ffffffffffffffffffffffffffffffffffffffff83166136ed5761025792505a917f000000000000000000000000000000000000000000000000000000000000000061349a565b906102579261339c56fea264697066735822122060e1d56686ce05ca705d6e7e70f9331898fcd50b77b9a298061109afc7114b3264736f6c63430008180033000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab000000000000000000000000430000000000000000000000000000000000000400000000000000000000000095c68c52bb12a43069973fdcd88e4e93d2142f10000000000000000000000000430000000000000000000000000000000000000200000000000000000000000043000000000000000000000000000000000000030000000000000000000000002c64e6ee1dd9fc2a0db6a6b1aa2c3f163c7a2c780000000000000000000000002536fe9ab3f511540f2f9e2ec2a805005c3dd8000000000000000000000000004066b9bd584b5fa88897194dabe3a37883ac35f7
Deployed Bytecode
0x6080604052600436101561001257600080fd5b60003560e01c80631fe543e31461016757806323452b9c146101625780632bb5a9e61461015d5780633bf7696e146101585780633e567539146101535780635452d9b11461014e578063590e1ae3146101495780635b6ac011146101445780635cb6dfff1461013f5780636f64c4c41461013a57806370799963146101355780637200b829146101305780637762df251461012b57806379131a19146101265780638da5cb5b146101215780639877fbf01461011c578063999927df146101175780639a86256514610112578063c0b6f5611461010d578063f01f7ce4146101085763ffefdd0f1461010357600080fd5b611735565b6116c6565b611592565b61118f565b61100a565b610fce565b610f7c565b610e5c565b610d29565b610bb9565b610b70565b610b01565b610aa1565b610998565b6106af565b610673565b610541565b6104e8565b61047f565b61033b565b61027e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176101b757604052565b61016c565b67ffffffffffffffff81116101b757604052565b610160810190811067ffffffffffffffff8211176101b757604052565b6060810190811067ffffffffffffffff8211176101b757604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176101b757604052565b60405190610257826101d0565b565b60405190610257826101ed565b67ffffffffffffffff81116101b75760051b60200190565b3461032b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5760243567ffffffffffffffff811161032b573660238201121561032b578060040135906102d982610266565b906102e76040519283610209565b8282526020926024602084019160051b8301019136831161032b57602401905b82821061031c5761031a84600435611771565b005b81358152908401908401610307565b600080fd5b600091031261032b57565b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261044357610373612582565b60025460ff8160a01c1661038681610446565b80156104195780610398600192610446565b146103ee575b507fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff600254166002557f8eca980489e87f7dba4f26917aa4bfc906eb3f2b4f7b4b9fd0ff2b8bb3e21ae38180a180f35b7fffffffffffffffffffffffff0000000000000000000000000000000000000000166002553861039e565b60046040517fccf69db7000000000000000000000000000000000000000000000000000000008152fd5b80fd5b6003111561045057565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5760ff60025460a01c166040516003821015610450576020918152f35b73ffffffffffffffffffffffffffffffffffffffff81160361032b57565b3461032b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576020610539600435610528816104ca565b61053460243582611e24565b6125cd565b604051908152f35b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261044357610579612582565b60ff60025460a01c1660038110156106465760020361061c576105bf7fffffffffffffffffffffffff000000000000000000000000000000000000000060015416600155565b6105ec7fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b604051600081527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc90602090a180f35b60046040517f045c5122000000000000000000000000000000000000000000000000000000008152fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576020610539600435611cea565b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261044357600281541461096e576002815533815260049081602052604080822090815461ffff8160501c169081156109465764ffffffffff808260281c16801561091e578451907f6fac17110000000000000000000000000000000000000000000000000000000082526020828a8173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa9081156109195761079b9289926108e8575b506127fc565b429116116108c057946107d87f4716cc7ed643b171db3dc2065ce09672ebba9964fe933779d9f862ae75934cba959660601c926001860154611cd7565b918061086957506107f36108509261082b92860154906124c2565b936108205a86337f000000000000000000000000430000000000000000000000000000000000000461349a565b5464ffffffffff1690565b915164ffffffffff909216825233602083015260408201929092529081906060820190565b0390a161085c33612815565b6108666001600055565b80f35b9361082b9161087e846108509597339061339c565b8101548061088d575b50610820565b6108ba905a90337f000000000000000000000000430000000000000000000000000000000000000461349a565b38610887565b8583517fc3f53662000000000000000000000000000000000000000000000000000000008152fd5b61090b91925060203d602011610912575b6109038183610209565b8101906127df565b9038610795565b503d6108f9565b611e18565b8785517f79ed4ed0000000000000000000000000000000000000000000000000000000008152fd5b8583517f0a6499c8000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1bbee726000000000000000000000000000000000000000000000000000000008152fd5b3461032b576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610443576109d0612582565b60025460ff8160a01c166003811015610a7457610a4a577fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674020000000000000000000000000000000000000000176002557f3ff05a45e46337fa1cbf20996d2eeb927280bce099f37252bcca1040609604ec8180a180f35b60046040517f74ed79ae000000000000000000000000000000000000000000000000000000008152fd5b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576004356000526003602052602073ffffffffffffffffffffffffffffffffffffffff60406000205416604051908152f35b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602060405173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d168152f35b3461032b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576020610539600435610bb0816104ca565b60243590611e24565b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57600254600160ff8260a01c16610bfd81610446565b03610cff5773ffffffffffffffffffffffffffffffffffffffff163303610cd557600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001633179055610c757fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b610ca27fffffffffffffffffffffffff000000000000000000000000000000000000000060025416600255565b6040513381527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc9080602081015b0390a1005b60046040517fafdcfb92000000000000000000000000000000000000000000000000000000008152fd5b60046040517f5e4f2826000000000000000000000000000000000000000000000000000000008152fd5b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b61018090610e3561025794969593966101a0830197610da184825164ffffffffff169052565b60208181015164ffffffffff169085015260408181015161ffff169085015260608181015173ffffffffffffffffffffffffffffffffffffffff16908501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e085015261010080820151908501526101208082015190850152610140809101519084015261016083019015159052565b01907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5773ffffffffffffffffffffffffffffffffffffffff600435610eac816104ca565b16600052600460205260406000206008610ec461024a565b91610f148154610efb64ffffffffff610ee6818416889064ffffffffff169052565b8260281c16602087019064ffffffffff169052565b605081901c61ffff166040860152606090811c90850152565b60018101546080840152600281015460a0840152600381015460c0840152600481015460e0840152600581015461010084015260068101546101208401526007810154610140840152015490610f7860405192839260ff8260081c92169084610d7b565b0390f35b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b5760206105396004356120ce565b3461032b576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57600435611046816104ca565b61104e612582565b6040517fe12f3a6100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000043000000000000000000000000000000000000031691908381602481865afa9081156109195760009161115c575b50806110d257005b6040517faad3ec9600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9290921660048301526024820152908290829060449082906000905af180156109195761113657005b8161031a92903d10611155575b61114d8183610209565b810190611e09565b503d611143565b6111739150843d86116111555761114d8183610209565b386110ca565b61ffff81160361032b57565b8015150361032b57565b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576004356111c581611179565b602435604435916111d5836104ca565b6064359260843560a4356111e881611185565b60c435917effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83169687840361032b5760026000541461096e57600260005561ffff9485871698611237898b6128d7565b61128161127c61127561126a3373ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b5460501c61ffff1690565b61ffff1690565b612a0f565b61128b8484612a3f565b611294816126ac565b6112a86112a18b8b611cd7565b9183611e24565b80821161156857826112b9916125cd565b1161153e5787956112c982612a9b565b6112dd6112d4612bdf565b98819c85612de8565b604080517fdd8bd8540000000000000000000000000000000000000000000000000000000081523060048201529790919082896024817f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d73ffffffffffffffffffffffffffffffffffffffff165afa918215610919578a8161147d947f030123afc603cda1c31a8f75478e406e4bcd6cb42996fc50b4e2fea4c8c834249f948f956115029e60009361150f575b5064ffffffffff966113e56113b060206113a6875161ffff1690565b96015161ffff1690565b966113db6113bc61024a565b64ffffffffff438d161681529a421660208c019064ffffffffff169052565b61ffff16898c0152565b73ffffffffffffffffffffffffffffffffffffffff8b16606089015260808801528a60a08801528b60c088015260e08701526101008601521661012084015216610140820152611433610259565b90815286151560208201527effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff881681840152336000908152600460205260409020612152565b612152565b5197889733438a9793947effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9692919a999561ffff61010099956101208c019d8c5273ffffffffffffffffffffffffffffffffffffffff80951660208d01521660408b015260608a015216608088015260a087015260c0860152151560e085015216910152565b0390a161031a6001600055565b611530919350893d8b11611537575b6115288183610209565b810190612118565b913861138a565b503d61151e565b60046040517f367b6990000000000000000000000000000000000000000000000000000000008152fd5b60046040517ffb167496000000000000000000000000000000000000000000000000000000008152fd5b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b576004356115cd816104ca565b6115d5612582565b60ff60025460a01c16600381101561045057610a4a57610cd07fb86c75c9bffca616b2d314cc914f7c3f1d174255b16b941c3f3ededee276d5ef91611654740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6002541617600255565b6116998173ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006002541617600255565b6040805133815273ffffffffffffffffffffffffffffffffffffffff909216602083015290918291820190565b3461032b5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602060405173ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab168152f35b3461032b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032b57602061053960043561270b565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000095c68c52bb12a43069973fdcd88e4e93d2142f108181163303611c03575060026000541461096e5760026000556117f16117d7836000526003602052604060002090565b5473ffffffffffffffffffffffffffffffffffffffff1690565b908116611806575b5050506102576001600055565b6118308173ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b91825461183f8160601c612f0f565b9081611be9575b5080611bdc575b611858575b506117f9565b61187361189b91959394956000526003602052604060002090565b7fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b6118a3612385565b936118ce6118ba611275865461ffff9060501c1690565b916118c4836123c4565b6080880152612413565b5160208601526118dd816123c4565b906118e6612463565b936002860154936003870154946008880154955b895185811015611b9f5761191360408c0151838561306f565b611b175761193a61193360208d01516a084595161401484a000000900690565b918861244f565b5289878a60ff821680611aef575b8015611ac4575b15611aa5576119cc926040611a23611a1a61199a946119d1611a2d9760081c6001830154998a936119cc6119c46119bc6119a461199a61198f8a89611cd7565b600687015490611cd7565b6305f5e100900490565b9c8d9460076119b38b8a611cd7565b91015490611cd7565b9d8e95611cd7565b612710900490565b611cb3565b6119e1608087015187519061244f565b526119f2608086015186519061244f565b51611a02606087019182516124c2565b9052611a14608086015186519061244f565b516124a9565b828401516124cf565b9101528a516124c2565b8952611a3e60208a019182516124c2565b90525b60208a0151604051611a8c81611a606020820194859190602083019252565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610209565b51902060208b0152611a9e8a5161247c565b8a526118fa565b611aba925060019150015460408c01516124a9565b60408b0152611a41565b5060ff821615801561194f5750611adc83518a61244f565b51611ae98360081c61270b565b1161194f565b50611afb83518a61244f565b51611b10611b0b8460081c61270b565b6120ce565b1115611948565b505050947fa4e48c2cf5e4a2bdcbde67ad67bb430348ef15d3451d2f546ce2d7f522e129ad9450611b8e91979250928593611b71611b96985b611b6486516060880151855191848b6130a9565b6108206004820154613273565b93608081015190519060208351930151936040519788978861251f565b0390a1612815565b38808080611852565b505050947fa4e48c2cf5e4a2bdcbde67ad67bb430348ef15d3451d2f546ce2d7f522e129ad9450611b8e91979250928593611b71611b9698611b50565b506005830154811461184d565b611bfd915060281c64ffffffffff16612fb8565b38611846565b6040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff919091166024820152604490fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b907ffffffffffffffffffffffffffffffffffffffffffff7ba6ae9ebfeb7b60000008201918211611cae57565b611c52565b91908203918211611cae57565b906103e891828102928184041490151715611cae57565b81810292918115918404141715611cae57565b611cf3816126ac565b611cfc8161270b565b6a084595161401484a00000091818303928311611cae57604051927fdd8bd85400000000000000000000000000000000000000000000000000000000845230600485015260408460248173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa90811561091957611dd5611dd0611dcb611dc6611de196611dc0611275611dbb6020611ddb9a611de79e600091611dea575b50015161ffff1690565b6126f7565b90611cd7565b611cc0565b612731565b611c81565b906127aa565b91612769565b90611cb3565b90565b611e03915060403d604011611537576115288183610209565b38611db1565b9081602091031261032b575190565b6040513d6000823e3d90fd5b611e2d826126ac565b8091600090611e3b836135c0565b9373ffffffffffffffffffffffffffffffffffffffff809416156120a7575b604051947f3e041d0f00000000000000000000000000000000000000000000000000000000865260209586816004818986165afa90811561091957859161207a575b506040517ff3f4370300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152908790829060249082908a165afa92831561091957869288928795612055575b506040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909116600482015292839190829081602481015b0392165afa90811561091957611f809491612038575b5081811061202b57611dc091611f7a91611cb3565b91611cea565b6040517faf49f220000000000000000000000000000000000000000000000000000000008152306004820152909290918190839060249082907f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa801561091957611de7936119c493611ffe9360009361200c575b5050611cd7565b670de0b6b3a7640000900490565b612023929350803d106111555761114d8183610209565b903880611ff7565b5050611dc0600091611cea565b61204f9150863d88116111555761114d8183610209565b38611f65565b611f4f9291955061207290843d86116111555761114d8183610209565b949091611eff565b61209a9150873d89116120a0575b6120928183610209565b8101906128ad565b38611e9c565b503d612088565b507f0000000000000000000000004300000000000000000000000000000000000004611e5a565b6a084595161401484a000000908181116120ee578103908111611cae5790565b60046040517f8b88ffad000000000000000000000000000000000000000000000000000000008152fd5b9081604091031261032b576020604051916121328361019b565b805161213d81611179565b8352015161214a81611179565b602082015290565b90612354604060086102579461014085516121a7612175825164ffffffffff1690565b849064ffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000825416179055565b6121f76121bc602083015164ffffffffff1690565b84547fffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffffff1660289190911b69ffffffffff000000000016178455565b6122456122088683015161ffff1690565b84547fffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffff1660509190911b6bffff0000000000000000000016178455565b6122a6612269606083015173ffffffffffffffffffffffffffffffffffffffff1690565b84546bffffffffffffffffffffffff1660609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016178455565b6080810151600184015560a0810151600284015560c0810151600384015560e081015160048401556101008101516005840155610120810151600684015501516007820155019261232e6122fd6020830151151590565b859060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b01517effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b6040519060a0820182811067ffffffffffffffff8211176101b75760405260606080836000815260006020820152600060408201526000838201520152565b906123ce82610266565b6123db6040519182610209565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06124098294610266565b0190602036910137565b8051156124205760200190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b80518210156124205760209160051b010190565b604051906124708261019b565b60006020838281520152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611cae5760010190565b81810392916000138015828513169184121617611cae57565b91908201809211611cae57565b91909160008382019384129112908015821691151617611cae57565b90815180825260208080930193019160005b82811061250b575050505090565b8351855293810193928101926001016124fd565b939060c0959897969373ffffffffffffffffffffffffffffffffffffffff6125739464ffffffffff6125659416885216602087015260e0604087015260e08601906124eb565b9084820360608601526124eb565b95608083015260a08201520152565b73ffffffffffffffffffffffffffffffffffffffff6001541633036125a357565b60046040517f30cd7471000000000000000000000000000000000000000000000000000000008152fd5b6040517f91c4508b00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff9092166024830152612710900491906020818060448101038173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa9081156109195760009161268d575b5082811080612684575b6126805750565b9150565b50801515612679565b6126a6915060203d6020116111555761114d8183610209565b3861266f565b61291e81109081156126ea575b506126c057565b60046040517fa0a21059000000000000000000000000000000000000000000000000000000008152fd5b62989680915011386126b9565b9061ffff80921661271003918211611cae57565b8015612723576c01431e0fae6d7217caa00000000490565b637c5f487d6000526004601cfd5b670de0b6b3a7640000612710917812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21811182021583021561272357020490565b670de0b6b3a76400006a084595161401484a000000917812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21811182021583021561272357020490565b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21811182021583021561272357020490565b9081602091031261032b575164ffffffffff8116810361032b5790565b91909164ffffffffff80809416911601918211611cae57565b6102579061147860405191612829836101d0565b60008084528060208501528060408501528060608501528060808501528060a08501528060c08501528060e08501528061010085015280610120850152806101408501526040519361287a856101ed565b8452806020850152604084015273ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b9081602091031261032b5751611de7816104ca565b9081602091031261032b5751611de781611179565b80156129e5576040517f6a50887100000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa80156109195761ffff916000916129b6575b50161061298c571561296257565b60046040517fcd57ebca000000000000000000000000000000000000000000000000000000008152fd5b60046040517f7e0307aa000000000000000000000000000000000000000000000000000000008152fd5b6129d8915060203d6020116129de575b6129d08183610209565b8101906128c2565b38612954565b503d6129c6565b60046040517f5ced2d51000000000000000000000000000000000000000000000000000000008152fd5b612a1557565b60046040517f9ad3d189000000000000000000000000000000000000000000000000000000008152fd5b600013908115612a7b575b50612a5157565b60046040517fb81df4da000000000000000000000000000000000000000000000000000000008152fd5b600091501338612a4a565b9081602091031261032b5751611de781611185565b602073ffffffffffffffffffffffffffffffffffffffff612abd6004936135c0565b16604051928380927f5c975abb0000000000000000000000000000000000000000000000000000000082525afa90811561091957600091612b2a575b50612b0057565b60046040517feaa69c55000000000000000000000000000000000000000000000000000000008152fd5b612b4c915060203d602011612b52575b612b448183610209565b810190612a86565b38612af9565b503d612b3a565b91908260c091031261032b578151612b70816104ca565b91602081015167ffffffffffffffff8116810361032b5791604082015163ffffffff8116810361032b57916060810151612ba981611179565b9160808201517dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116810361032b5760a09092015190565b73ffffffffffffffffffffffffffffffffffffffff6040517fa5d37e8d00000000000000000000000000000000000000000000000000000000815260c081600481857f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa90811561091957600091828093819282948391612da5575b506020949596600091612cd2604051998a97889687947f5d3b1d300000000000000000000000000000000000000000000000000000000086526004860190949363ffffffff9061ffff60019567ffffffffffffffff60809660a087019a87521660208601521660408401521660608201520152565b0393165af191821561091957600092612d64575b507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90612d6083612d2033916000526003602052604060002090565b9073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b1691565b7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff919250612d9e9060203d6020116111555761114d8183610209565b9190612ce6565b925050506000935060209250612dd3915060c03d60c011612de1575b612dcb8183610209565b810190612b59565b929650949093929190612c5d565b503d612dc1565b91929173ffffffffffffffffffffffffffffffffffffffff808216612e4d575050612e1690612e1b93611cd7565b6124c2565b3403612e2357565b60046040517fe3057cf1000000000000000000000000000000000000000000000000000000008152fd5b9190923403612e2357612e82917f00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab1693611cd7565b823b1561032b576040517fda3e8ce400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921660048301523360248301523060448301526064820152906000908290818381608481015b03925af1801561091957612efc5750565b80612f09610257926101bc565b80610330565b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91821660248201526020816044817f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d86165afa90811561091957600091612f99575b5016151590565b612fb2915060203d6020116120a0576120928183610209565b38612f92565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed464ffffffffff80921601818111611cae57604051907f6fac171100000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa90811561091957613068926000926108e857506127fc565b4291161190565b9190821515928361309e575b50821561308757505090565b8015159250908261309757505090565b1315905090565b82121592503861307b565b9093926130ef906130e66001870154946130e08854936130da6130d461ffff8760501c169485611cb3565b89611cd7565b906124c2565b95611cd7565b9060601c61369a565b816131a4575b505080613100575050565b61314273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d16925460601c90565b90823b1561032b576040517f7c0efbd700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9092166004830152602482015290600090829081838160448101612eeb565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d16906131e7855460601c90565b91803b1561032b576040517f7d1d27f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff93841660048201526024810194909452911660448301526000908290606490829084905af1801561091957613260575b806130f5565b80612f0961326d926101bc565b3861325a565b8061327b5750565b604051907fcaf7564a00000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610919576102579260009261331d575b505a917f000000000000000000000000430000000000000000000000000000000000000461349a565b61333791925060203d6020116120a0576120928183610209565b90386132f4565b3d15613397573d9067ffffffffffffffff82116101b7576040519161338b60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610209565b82523d6000602084013e565b606090565b919091803b15613470576040517fa9059cbb000000000000000000000000000000000000000000000000000000006020820190815273ffffffffffffffffffffffffffffffffffffffff909416602482015260448101929092526000928392839061340a8160648101611a60565b51925af161341661333e565b901561344657805180613427575050565b8160208061343c936134409501019101612a86565b1590565b61344657565b60046040517ff1568f95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f09ee12d5000000000000000000000000000000000000000000000000000000008152fd5b6134af82849395600080809781948294f11590565b6134ba575b50505050565b73ffffffffffffffffffffffffffffffffffffffff16803b156135bc57604051937fd0e30db0000000000000000000000000000000000000000000000000000000008552838560048186865af193841561091957613573956020956135a9575b506040518096819582947fa9059cbb000000000000000000000000000000000000000000000000000000008452600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af180156109195761358a575b8080806134b4565b6135a29060203d602011612b5257612b448183610209565b5038613582565b80612f096135b6926101bc565b3861351a565b8280fd5b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff918216602482015291906020836044817f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d85165afa92831561091957600093613679575b5082161561364f57565b60046040517f4daebedc000000000000000000000000000000000000000000000000000000008152fd5b61369391935060203d6020116120a0576120928183610209565b9138613645565b906136a4826135c0565b9073ffffffffffffffffffffffffffffffffffffffff83166136ed5761025792505a917f000000000000000000000000430000000000000000000000000000000000000461349a565b906102579261339c56fea264697066735822122060e1d56686ce05ca705d6e7e70f9331898fcd50b77b9a298061109afc7114b3264736f6c63430008180033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab000000000000000000000000430000000000000000000000000000000000000400000000000000000000000095c68c52bb12a43069973fdcd88e4e93d2142f10000000000000000000000000430000000000000000000000000000000000000200000000000000000000000043000000000000000000000000000000000000030000000000000000000000002c64e6ee1dd9fc2a0db6a6b1aa2c3f163c7a2c780000000000000000000000002536fe9ab3f511540f2f9e2ec2a805005c3dd8000000000000000000000000004066b9bd584b5fa88897194dabe3a37883ac35f7
-----Decoded View---------------
Arg [0] : _gameConfigurationManager (address): 0x572a1FA9e45c2ec681ABa11B9Fdb829A5Ba9E50d
Arg [1] : _transferManager (address): 0x00000000007FE8d7666BB0da2A5D13f72b8dABaB
Arg [2] : _weth (address): 0x4300000000000000000000000000000000000004
Arg [3] : _vrfCoordinator (address): 0x95c68c52bb12a43069973FDCD88e4e93d2142f10
Arg [4] : _blast (address): 0x4300000000000000000000000000000000000002
Arg [5] : _usdb (address): 0x4300000000000000000000000000000000000003
Arg [6] : _owner (address): 0x2C64e6Ee1Dd9Fc2a0Db6a6B1aa2c3f163C7A2C78
Arg [7] : _blastPoints (address): 0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800
Arg [8] : _blastPointsOperator (address): 0x4066b9BD584b5FA88897194dAbE3a37883AC35F7
-----Encoded View---------------
9 Constructor Arguments found :
Arg [0] : 000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d
Arg [1] : 00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab
Arg [2] : 0000000000000000000000004300000000000000000000000000000000000004
Arg [3] : 00000000000000000000000095c68c52bb12a43069973fdcd88e4e93d2142f10
Arg [4] : 0000000000000000000000004300000000000000000000000000000000000002
Arg [5] : 0000000000000000000000004300000000000000000000000000000000000003
Arg [6] : 0000000000000000000000002c64e6ee1dd9fc2a0db6a6b1aa2c3f163c7a2c78
Arg [7] : 0000000000000000000000002536fe9ab3f511540f2f9e2ec2a805005c3dd800
Arg [8] : 0000000000000000000000004066b9bd584b5fa88897194dabe3a37883ac35f7
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$196.51
Net Worth in ETH
0.066539
Token Allocations
ETH
100.00%
POL
0.00%
Multichain Portfolio | 35 Chains
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.