Source Code
Latest 25 from a total of 690,205 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Refund | 29857364 | 10 days ago | IN | 0 ETH | 0.00000011 | ||||
| Play | 29826442 | 10 days ago | IN | 0.0050017 ETH | 0.00000043 | ||||
| Claim Yield | 29507665 | 18 days ago | IN | 0 ETH | 0.00000275 | ||||
| Play | 29192699 | 25 days ago | IN | 0.0000017 ETH | 0.00000156 | ||||
| Play | 28719501 | 36 days ago | IN | 0.0000017 ETH | 0.00000005 | ||||
| Play | 28557462 | 40 days ago | IN | 0.0001017 ETH | 0.00000007 | ||||
| Refund | 28557445 | 40 days ago | IN | 0 ETH | 0.00000004 | ||||
| Play | 28510182 | 41 days ago | IN | 0.0001017 ETH | 0.00000013 | ||||
| Refund | 28510164 | 41 days ago | IN | 0 ETH | 0.00000009 | ||||
| Play | 28481039 | 41 days ago | IN | 0.0008017 ETH | 0.0000001 | ||||
| Refund | 28481004 | 41 days ago | IN | 0 ETH | 0.00000006 | ||||
| Play | 28473536 | 42 days ago | IN | 0.0016017 ETH | 0.00000011 | ||||
| Refund | 28258284 | 47 days ago | IN | 0 ETH | 0.00005104 | ||||
| Play | 28224306 | 47 days ago | IN | 0.00009935 ETH | 0 | ||||
| Refund | 28215451 | 48 days ago | IN | 0 ETH | 0 | ||||
| Play | 28208092 | 48 days ago | IN | 0.00019701 ETH | 0 | ||||
| Refund | 28121832 | 50 days ago | IN | 0 ETH | 0 | ||||
| Play | 28054583 | 51 days ago | IN | 0.00001146 ETH | 0 | ||||
| Refund | 27969581 | 53 days ago | IN | 0 ETH | 0 | ||||
| Play | 27956712 | 54 days ago | IN | 0.00009935 ETH | 0 | ||||
| Refund | 27943356 | 54 days ago | IN | 0 ETH | 0 | ||||
| Play | 27933275 | 54 days ago | IN | 0.00019701 ETH | 0.00000031 | ||||
| Refund | 27920458 | 54 days ago | IN | 0 ETH | 0 | ||||
| Play | 27919198 | 54 days ago | IN | 0.00009935 ETH | 0 | ||||
| Refund | 27919187 | 54 days ago | IN | 0 ETH | 0 |
Advanced mode: Intended for advanced users or developers and will display all Internal Transactions including zero value transfers.
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Block | From | To | ||||
|---|---|---|---|---|---|---|---|
| 29857364 | 10 days ago | 0.0050017 ETH | |||||
| 29857364 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29826442 | 10 days ago | 0 ETH | |||||
| 29709399 | 13 days ago | 0 ETH | |||||
| 29709399 | 13 days ago | 0 ETH | |||||
| 29709399 | 13 days ago | 0 ETH | |||||
| 29705835 | 13 days ago | 0 ETH | |||||
| 29705835 | 13 days ago | 0 ETH | |||||
| 29705835 | 13 days ago | 0 ETH | |||||
| 29704617 | 13 days ago | 0 ETH | |||||
| 29704617 | 13 days ago | 0 ETH | |||||
| 29704617 | 13 days ago | 0 ETH | |||||
| 29704264 | 13 days ago | 0 ETH | |||||
| 29704264 | 13 days ago | 0 ETH |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
LaserBlast
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 {Game} from "./Game.sol";
import {IGameConfigurationManager} from "./interfaces/IGameConfigurationManager.sol";
/**
* @title LaserBlast
* @notice Players can play laserBlast against a liquidity pool via this contract.
* @author YOLO Games protocol team
*/
contract LaserBlast is Game {
/**
* @notice The minimum number of rows in the LaserBlast machine
*/
uint256 private constant MINIMUM_NUMBER_OF_ROWS = 8;
/**
* @notice The maximum number of rows in the LaserBlast machine
*/
uint256 private constant MAXIMUM_NUMBER_OF_ROWS = 16;
/**
* @notice The lowest risk level
*/
uint256 private constant STARTING_RISK_LEVEL = 1;
/**
* @notice The highest risk level
*/
uint256 private constant HIGHEST_RISK_LEVEL = 3;
/**
* @dev The LaserBlast game struct
* @param params The game parametersb
* @param riskLevel The risk level
* @param rowCount The number of rows in the LaserBlast machine
*/
struct LaserBlast__Game {
Game__GameParams params;
uint128 riskLevel;
uint128 rowCount;
}
mapping(address player => LaserBlast__Game) public games;
/**
* @dev The key is the hash of the risk level, the row count and the bin
*/
mapping(bytes32 key => uint256 multiplier) private multipliers;
/**
* @dev The Kelly fractions for each risk level. Each fraction is scaled by 1e6.
*/
uint256[9][3] public kellyFractions;
event LaserBlast__GameCreated(
uint256 blockNumber,
address player,
uint256 numberOfRounds,
uint256 playAmountPerRound,
address currency,
int256 stopGain,
int256 stopLoss,
uint256 riskLevel,
uint256 rowCount
);
event LaserBlast__GameCompleted(
uint256 blockNumber,
address player,
uint256[] results,
uint256[] payouts,
uint256 numberOfRoundsPlayed,
uint256 protocolFee,
uint256 liquidityPoolFee
);
event LaserBlast__MultipliersSet(uint256 riskLevel, uint256 rowCount, uint256[] multipliers);
event LaserBlast__KellyFractionsUpdated(uint256[9][3] _kellyFractions);
error LaserBlast__InvalidRiskLevel();
error LaserBlast__InvalidRowCount();
error LaserBlast__MultiplierAlreadySet();
error LaserBlast__BinsCountMustBeOneHigherThanRowCount();
/**
* @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 Set the multipliers for each risk level / row count / multiplier combination.
* Once set, the multipliers cannot be changed.
* Only callable by contract owner.
* @param riskLevel The risk level to set multipliers for
* @param rowCount The number of rows in the LaserBlast machine
* @param _multipliers The multipliers for each bin
*/
function setMultipliers(uint128 riskLevel, uint128 rowCount, uint256[] memory _multipliers) external onlyOwner {
_validateRiskLevel(riskLevel);
_validateRowCount(rowCount);
uint256 binsCount = _multipliers.length;
if (binsCount != rowCount + 1) {
revert LaserBlast__BinsCountMustBeOneHigherThanRowCount();
}
for (uint256 i; i < binsCount; ++i) {
uint256 multiplier = _multipliers[i];
if (multiplier == 0) {
revert Game__ZeroMultiplier();
}
bytes32 key = _multiplierKey(riskLevel, rowCount, i);
if (multipliers[key] != 0) {
revert LaserBlast__MultiplierAlreadySet();
}
multipliers[key] = multiplier;
}
emit LaserBlast__MultipliersSet(riskLevel, rowCount, _multipliers);
}
/**
* @notice Set the Kelly fractions for each risk level / row count combination. Only callable by contract owner.
*
* @param _kellyFractions The Kelly fractions for each risk level / row count combination
*/
function setKellyFractions(uint256[9][3] memory _kellyFractions) external onlyOwner {
_setKellyFractions(_kellyFractions);
}
/**
* @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 riskLevel The risk level to play with
* @param rowCount The number of rows in the LaserBlast machine
*/
function play(
uint16 numberOfRounds,
uint256 playAmountPerRound,
address currency,
int256 stopGain,
int256 stopLoss,
uint128 riskLevel,
uint128 rowCount
) external payable nonReentrant {
_validateNumberOfRoundsAndPlayAmountPerRound(numberOfRounds, playAmountPerRound);
_validateNoOngoingRound(games[msg.sender].params.numberOfRounds);
_validateStopGainAndLoss(stopGain, stopLoss);
_validateMultiplierIsSet(riskLevel, rowCount);
uint256 totalPlayAmount = playAmountPerRound * numberOfRounds;
uint256 _maxPlayAmountPerGame = maxPlayAmountPerGame(currency, riskLevel, rowCount);
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] = LaserBlast__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
}),
riskLevel: riskLevel,
rowCount: rowCount
});
emit LaserBlast__GameCreated(
block.number,
msg.sender,
numberOfRounds,
playAmountPerRound,
currency,
stopGain,
stopLoss,
riskLevel,
rowCount
);
}
/**
* @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 Get the multipliers for the given risk level and row count
* @param riskLevel The risk level to query
* @param rowCount The row count to query
* @return _multipliers The multipliers for the game configuration
*/
function getMultipliers(uint128 riskLevel, uint128 rowCount) external view returns (uint256[] memory _multipliers) {
_validateRiskLevel(riskLevel);
_validateRowCount(rowCount);
uint256 binsCount = rowCount + 1;
_multipliers = new uint256[](binsCount);
for (uint256 i; i < binsCount; ++i) {
_multipliers[i] = multipliers[_multiplierKey(riskLevel, rowCount, i)];
}
}
/**
* @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 riskLevel The risk level to query
* @param rowCount The row count to query
* @return maxPlayAmount The maximum play amount per game
*/
function maxPlayAmountPerGame(
address currency,
uint128 riskLevel,
uint128 rowCount
) public view returns (uint256 maxPlayAmount) {
_validateRiskLevel(riskLevel);
_validateRowCount(rowCount);
/**
* 1e6 is the scaling factor for the Kelly fractions
* 10_000 is the scaling factor for the Kelly Fraction basis points
*
* So 1e10
*/
maxPlayAmount =
(_liquidityPoolBalance(currency) *
kellyFractions[riskLevel - STARTING_RISK_LEVEL][rowCount - MINIMUM_NUMBER_OF_ROWS] *
GAME_CONFIGURATION_MANAGER.kellyFractionBasisPoints(address(this))) /
1e10;
}
/**
* @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 riskLevel The risk level to query
* @param rowCount The row count to query
*/
function minPlayAmountPerGame(
address currency,
uint128 riskLevel,
uint128 rowCount
) public view returns (uint256 minPlayAmount) {
minPlayAmount = _minPlayAmountPerGame(maxPlayAmountPerGame(currency, riskLevel, rowCount), currency);
}
/**
* @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)) {
LaserBlast__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);
uint256 adjustedLiquidityPoolFeeBasisPoints = _applyMultiplierToLiquidityPoolFeeBasisPoints(
game.params.liquidityPoolFeeBasisPoints,
game.riskLevel
);
Fee memory fee;
uint256 playAmountPerRound = game.params.playAmountPerRound;
for (
;
runningGameState.numberOfRoundsPlayed < game.params.numberOfRounds;
++runningGameState.numberOfRoundsPlayed
) {
if (
_stopGainOrStopLossHit(game.params.stopGain, game.params.stopLoss, runningGameState.netAmount)
) {
break;
}
(uint256 result, uint256 multiplier, uint256 randomWordForNextRound) = _dropTheBall(
game.riskLevel,
game.rowCount,
runningGameState.randomWord
);
uint256 protocolFee = (playAmountPerRound * multiplier * game.params.protocolFeeBasisPoints) / 1e8;
uint256 liquidityPoolFee = (playAmountPerRound * multiplier * adjustedLiquidityPoolFeeBasisPoints) /
1e8;
runningGameState.payouts[runningGameState.numberOfRoundsPlayed] =
(playAmountPerRound * multiplier) /
10_000 -
protocolFee -
liquidityPoolFee;
runningGameState.payout += runningGameState.payouts[runningGameState.numberOfRoundsPlayed];
runningGameState.netAmount += (int256(
runningGameState.payouts[runningGameState.numberOfRoundsPlayed]
) - int256(playAmountPerRound));
fee.protocolFee += protocolFee;
fee.liquidityPoolFee += liquidityPoolFee;
results[runningGameState.numberOfRoundsPlayed] = result;
runningGameState.randomWord = randomWordForNextRound;
}
_handlePayout(
player,
game.params,
runningGameState.numberOfRoundsPlayed,
runningGameState.payout,
fee.protocolFee
);
_transferVrfFee(game.params.vrfFee);
emit LaserBlast__GameCompleted(
game.params.blockNumber,
player,
results,
runningGameState.payouts,
runningGameState.numberOfRoundsPlayed,
fee.protocolFee,
fee.liquidityPoolFee
);
_deleteGame(player);
}
}
}
/**
* @param riskLevel The risk level
* @param rowCount The row count
* @param randomWord The random word
* @return result The result of the game. Each bit represents a movement , starting from the least significant bit.
* 0 represents a leftward movement and 1 represents a rightward movement.
* @return multiplier The bin where the ball ends up at's multiplier
* @return randomWordForNextRound The random word for next round
*/
function _dropTheBall(
uint256 riskLevel,
uint256 rowCount,
uint256 randomWord
) private view returns (uint256 result, uint256 multiplier, uint256 randomWordForNextRound) {
int256 movement;
for (uint256 i; i < rowCount; ++i) {
if (randomWord % 2 == 0) {
movement -= 1;
} else {
movement += 1;
result |= (1 << i);
}
randomWord = uint256(keccak256(abi.encode(randomWord)));
}
uint256 bin = uint256(movement + int256(rowCount)) / 2;
multiplier = multipliers[_multiplierKey(riskLevel, rowCount, bin)];
randomWordForNextRound = randomWord;
}
/**
* @param player The player
*/
function _deleteGame(address player) private {
games[player] = LaserBlast__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
}),
riskLevel: 0,
rowCount: 0
});
}
/**
* @param riskLevel The risk level
*/
function _validateRiskLevel(uint256 riskLevel) private pure {
if (riskLevel < STARTING_RISK_LEVEL || riskLevel > HIGHEST_RISK_LEVEL) {
revert LaserBlast__InvalidRiskLevel();
}
}
/**
* @param rowCount The row count
*/
function _validateRowCount(uint256 rowCount) private pure {
if (rowCount < MINIMUM_NUMBER_OF_ROWS || rowCount > MAXIMUM_NUMBER_OF_ROWS) {
revert LaserBlast__InvalidRowCount();
}
}
/**
* @dev Validate the multiplier is set for the given risk level and row count. We only need to check the first bin
* because we trust that setMultipliers functions correctly.
* @param riskLevel The risk level to validate
* @param rowCount The row count to validate
*/
function _validateMultiplierIsSet(uint256 riskLevel, uint256 rowCount) private view {
if (multipliers[_multiplierKey(riskLevel, rowCount, 0)] == 0) {
revert Game__ZeroMultiplier();
}
}
/**
* @dev The multiplier key is the hash of the risk level, the row count and the bin
* @param riskLevel The risk level
* @param rowCount The row count
* @param bin The bin
* @return key The multiplier key
*/
function _multiplierKey(uint256 riskLevel, uint256 rowCount, uint256 bin) private pure returns (bytes32 key) {
key = keccak256(abi.encode(riskLevel, rowCount, bin));
}
/**
* @dev Risk level 2 gets a liquidity pool fee multiplier of 1.5x and risk level 3 gets a multiplier of 2x.
* @param liquidityPoolFeeBasisPoints The liquidity pool fee basis points
* @param riskLevel The game's risk level
* @return adjustedLiquidityPoolFeeBasisPoints The adjusted liquidity pool fee basis points
*/
function _applyMultiplierToLiquidityPoolFeeBasisPoints(
uint256 liquidityPoolFeeBasisPoints,
uint256 riskLevel
) private pure returns (uint256 adjustedLiquidityPoolFeeBasisPoints) {
if (riskLevel == 1) {
adjustedLiquidityPoolFeeBasisPoints = liquidityPoolFeeBasisPoints;
} else if (riskLevel == 2) {
adjustedLiquidityPoolFeeBasisPoints = (liquidityPoolFeeBasisPoints * 3) / 2;
} else if (riskLevel == 3) {
adjustedLiquidityPoolFeeBasisPoints = liquidityPoolFeeBasisPoints * 2;
}
}
/**
* @dev Set the Kelly fractions for each risk level / row count combination.
*
* @param _kellyFractions The Kelly fractions for each risk level / row count combination
*/
function _setKellyFractions(uint256[9][3] memory _kellyFractions) private {
for (uint256 riskLevel = STARTING_RISK_LEVEL; riskLevel <= HIGHEST_RISK_LEVEL; ++riskLevel) {
for (uint256 rowCount = MINIMUM_NUMBER_OF_ROWS; rowCount <= MAXIMUM_NUMBER_OF_ROWS; ++rowCount) {
uint256 kellyFraction = _kellyFractions[riskLevel - 1][rowCount - MINIMUM_NUMBER_OF_ROWS];
if (kellyFraction == 0) {
revert Game__ZeroKellyFraction();
}
kellyFractions[riskLevel - 1][rowCount - MINIMUM_NUMBER_OF_ROWS] = kellyFraction;
}
}
emit LaserBlast__KellyFractionsUpdated(_kellyFractions);
}
}// 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();
}
}
}{
"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":"LaserBlast__BinsCountMustBeOneHigherThanRowCount","type":"error"},{"inputs":[],"name":"LaserBlast__InvalidRiskLevel","type":"error"},{"inputs":[],"name":"LaserBlast__InvalidRowCount","type":"error"},{"inputs":[],"name":"LaserBlast__MultiplierAlreadySet","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":"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":"LaserBlast__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":"uint256","name":"riskLevel","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rowCount","type":"uint256"}],"name":"LaserBlast__GameCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[9][3]","name":"_kellyFractions","type":"uint256[9][3]"}],"name":"LaserBlast__KellyFractionsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"riskLevel","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rowCount","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"multipliers","type":"uint256[]"}],"name":"LaserBlast__MultipliersSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"NewOwner","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":[],"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":"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":"uint128","name":"riskLevel","type":"uint128"},{"internalType":"uint128","name":"rowCount","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"riskLevel","type":"uint128"},{"internalType":"uint128","name":"rowCount","type":"uint128"}],"name":"getMultipliers","outputs":[{"internalType":"uint256[]","name":"_multipliers","type":"uint256[]"}],"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":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"kellyFractions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint128","name":"riskLevel","type":"uint128"},{"internalType":"uint128","name":"rowCount","type":"uint128"}],"name":"maxPlayAmountPerGame","outputs":[{"internalType":"uint256","name":"maxPlayAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint128","name":"riskLevel","type":"uint128"},{"internalType":"uint128","name":"rowCount","type":"uint128"}],"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":"uint128","name":"riskLevel","type":"uint128"},{"internalType":"uint128","name":"rowCount","type":"uint128"}],"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"},{"inputs":[{"internalType":"uint256[9][3]","name":"_kellyFractions","type":"uint256[9][3]"}],"name":"setKellyFractions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"riskLevel","type":"uint128"},{"internalType":"uint128","name":"rowCount","type":"uint128"},{"internalType":"uint256[]","name":"_multipliers","type":"uint256[]"}],"name":"setMultipliers","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
610120604081815234620003fd576000610120836200413e80380380916200002882856200042c565b833981010312620002e0576200003e8362000450565b6020906200004e85830162000450565b6200005b86860162000450565b620000696060880162000450565b9686620000796080830162000450565b6200008760a0840162000450565b9460c09a8b8501620000999062000450565b95620000a860e0870162000450565b9561010001620000b89062000450565b6001808d55608084905280546001600160a01b0319166001600160a01b03998a169081179091559551868152979990978a9182917f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc908e90a116938460a052168d5260e05286610100528b8b51809363a5d37e8d60e01b8252815a91600492fa918215620003f3579088918b936200035c575b5081169116036200034b57851690813b1562000347578791606483928b51948593849263c8992e6160e01b8452600260048501526001602485015260448401525af180156200033d5762000327575b50831690813b1562000323578360248792838a5195869485936336b91f2b60e01b85521660048401525af180156200031957918593918593620002f5575b508651631a33757d60e01b815260026004820152938492602492849291165af18015620002eb57620002b9575b50505051613cd8918262000466833960805182610396015260a051828181610cf401528181611206015281816115b7015281816122bc01528181612ae001528181612d1e01528181612f2c015281816133da015281816134a8015281816136c5015281816137660152818161386a015261393c015251818181611eb90152613165015260e0518181816112a101528181611330015281816123aa015281816138a40152613c7401526101005181611a870152f35b813d8311620002e3575b620002cf81836200042c565b81010312620002e057808062000205565b80fd5b503d620002c3565b84513d85823e3d90fd5b6200030591935093919362000402565b62000315578391839138620001d8565b8380fd5b86513d87823e3d90fd5b8580fd5b956200033585929762000402565b95906200019a565b88513d89823e3d90fd5b8780fd5b885163240e9d9560e01b8152600490fd5b8d809294508193503d8311620003eb575b6200037981836200042c565b81010312620003e3576200038d8162000450565b898201519091906001600160401b03811603620003e7578b81015163ffffffff811603620003e757606081015161ffff811603620003e757608001516001600160f01b03811603620003e357908790816200014b565b8980fd5b8a80fd5b503d6200036d565b8b513d8c823e3d90fd5b600080fd5b6001600160401b0381116200041657604052565b634e487b7160e01b600052604160045260246000fd5b601f909101601f19168101906001600160401b038211908210176200041657604052565b51906001600160a01b0382168203620003fd5756fe6080604052600436101561001257600080fd5b60003560e01c80631fe543e31461017757806323452b9c14610172578063239bcf501461016d578063285b9931146101685780632bb5a9e6146101635780633ccbad311461015e5780633e567539146101595780634b144d9414610154578063590e1ae31461014f5780635b6ac0111461014a5780635cb6dfff146101455780636f64c4c4146101405780637200b8291461013b5780637762df251461013657806379131a19146101315780638da5cb5b1461012c578063999927df14610127578063baebb0ee14610122578063c0b6f5611461011d578063f01f7ce414610118578063f0eb5cc0146101135763f8569e241461010e57600080fd5b611fea565b611f25565b611e6e565b611d3a565b611b6f565b611a00565b6119ae565b61187f565b61174b565b6115db565b61156c565b61150c565b611403565b611154565b61109b565b610f69565b610b88565b610b31565b610a90565b610a16565b61080c565b61032c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176101c757604052565b61017c565b67ffffffffffffffff81116101c757604052565b610160810190811067ffffffffffffffff8211176101c757604052565b6060810190811067ffffffffffffffff8211176101c757604052565b610120810190811067ffffffffffffffff8211176101c757604052565b6080810190811067ffffffffffffffff8211176101c757604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176101c757604052565b604051906102a0826101e0565b565b604051906102a0826101fd565b67ffffffffffffffff81116101c75760051b60200190565b9080601f830112156103275760209082356102e1816102af565b936102ef6040519586610252565b81855260208086019260051b82010192831161032757602001905b828210610318575050505090565b8135815290830190830161030a565b600080fd5b346103275760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103275760043560243567ffffffffffffffff81116103275761037e9036906004016102c7565b73ffffffffffffffffffffffffffffffffffffffff917f000000000000000000000000000000000000000000000000000000000000000083811633036107b257506002600054146107885760026000556103ff6103e5826000526003602052604060002090565b5473ffffffffffffffffffffffffffffffffffffffff1690565b928316610413575b6104116001600055565b005b61043d8373ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b90815461044c8160601c613389565b908161076e575b5080610761575b610465575b50610407565b61047c6104a4916000526003602052604060002090565b7fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b6104ac612838565b926104de6104ca6104c3845461ffff9060501c1690565b61ffff1690565b936104d4856127e9565b6080870152612797565b5160208501526104ed836127e9565b906007830154936105166008850154956fffffffffffffffffffffffffffffffff8716906134e9565b9461051f612877565b92600186015495600281015460038201546006830154955b808c5110156107215761054f60408d01518385613542565b61068b578a8c8b60208201518960801c906fffffffffffffffffffffffffffffffff8b169161057d9261357c565b94919390928c61058d85836120a9565b90610597916120a9565b6305f5e10090049384936105ab82846120a9565b906105b5916120a9565b6305f5e1009004946105c88692846120a9565b6127109004906105d79161209c565b906105e19161209c565b608083015183516105f1916127a4565b5260808201518251610602916127a4565b51606083015190610612916128bd565b606083015260808201518251610627916127a4565b5190610632916128fa565b60408201519061064191612913565b90604001528b5190610652916128bd565b8b5260208b015190610663916128bd565b60208b01528d51610674908c6127a4565b5260208d01526106848c51612890565b8c52610537565b5050508197507fd3057f299a2918324f2f221be0aa7b40f5fce02abeb8c60a83cd3baa9d550097965061071093506107189894919592506106f3905b6106db86516060880151855191848b613654565b6106e8600482015461381e565b5464ffffffffff1690565b93608081015190519060208351930151936040519788978861292f565b0390a16132c8565b3880808061045f565b5050508197507fd3057f299a2918324f2f221be0aa7b40f5fce02abeb8c60a83cd3baa9d550097965061071093506107189894919592506106f3906106c7565b506005820154811461045a565b610782915060281c64ffffffffff16613432565b38610453565b60046040517f1bbee726000000000000000000000000000000000000000000000000000000008152fd5b6040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff919091166024820152604490fd5b600091031261032757565b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261091457610844612992565b60025460ff8160a01c1661085781610af8565b80156108ea5780610869600192610af8565b146108bf575b507fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff600254166002557f8eca980489e87f7dba4f26917aa4bfc906eb3f2b4f7b4b9fd0ff2b8bb3e21ae38180a180f35b7fffffffffffffffffffffffff0000000000000000000000000000000000000000166002553861086f565b60046040517fccf69db7000000000000000000000000000000000000000000000000000000008152fd5b80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361032757565b60a435906fffffffffffffffffffffffffffffffff8216820361032757565b60c435906fffffffffffffffffffffffffffffffff8216820361032757565b600435906fffffffffffffffffffffffffffffffff8216820361032757565b602435906fffffffffffffffffffffffffffffffff8216820361032757565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6060910112610327576004356109e781610917565b906fffffffffffffffffffffffffffffffff906024358281168103610327579160443590811681036103275790565b34610327576020610a2f610a29366109b1565b916120d7565b604051908152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6003811015610a7b5760090260060190600090565b610a37565b6009821015610a7b570190600090565b346103275760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610327576024356004356003811015610327576009821015610327576020916009610ae99202600601610a80565b90549060031b1c604051908152f35b60031115610b0257565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103275760ff60025460a01c166040516003821015610b02576020918152f35b61ffff81160361032757565b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600435610bbe81610b7c565b60243560443591610bce83610917565b606435608435610bdc610935565b610be4610954565b9160026000541461078857600260005561ffff9384861697610c06888a612a97565b610c49610c446104c3610c393373ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b5460501c61ffff1690565b612bcf565b610c538383612bff565b610c726fffffffffffffffffffffffffffffffff808716908616612c46565b610c7c89896120a9565b610c878686846120d7565b808211610f3f5782610c9891612cad565b11610f15578795610ca882612da4565b610cbc610cb3612ee8565b98819c856130f1565b604080517fdd8bd8540000000000000000000000000000000000000000000000000000000081523060048201529790919082896024817f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff165afa918215610f10578a81610e5d947f500b76c50bb3487d6c0addddaa452c56bfecb1202117bb7852c34c8acd6b78979f948f95610ed49e600093610ee1575b5064ffffffffff96610dc4610d8f6020610d85875161ffff1690565b96015161ffff1690565b96610dba610d9b610293565b64ffffffffff438d161681529a421660208c019064ffffffffff169052565b61ffff16898c0152565b73ffffffffffffffffffffffffffffffffffffffff8b16606089015260808801528a60a08801528b60c088015260e08701526101008601521661012084015216610140820152610e126102a2565b9081526fffffffffffffffffffffffffffffffff871660208201526fffffffffffffffffffffffffffffffff88168184015233600090815260046020526040902061240a565b61240a565b5197889733438a9795929361ffff61010098959b9a96929b6101208b019c8b5273ffffffffffffffffffffffffffffffffffffffff80951660208c01521660408a0152606089015216608087015260a086015260c08501526fffffffffffffffffffffffffffffffff80921660e085015216910152565b0390a16104116001600055565b610f02919350893d8b11610f09575b610efa8183610252565b8101906123d0565b9138610d69565b503d610ef0565b6120cb565b60046040517f367b6990000000000000000000000000000000000000000000000000000000008152fd5b60046040517ffb167496000000000000000000000000000000000000000000000000000000008152fd5b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261091457610fa1612992565b60ff60025460a01c16600381101561106e5760020361104457610fe77fffffffffffffffffffffffff000000000000000000000000000000000000000060015416600155565b6110147fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b604051600081527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc90602090a180f35b60046040517f045c5122000000000000000000000000000000000000000000000000000000008152fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b34610327576103607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757366023121561032757604080516110e1816101fd565b809161036490368211610327576004935b8285106111025761041184612659565b36601f8601121561032757815161111881610219565b8061012087013681116103275787915b81831061114457505050816020916101209352019401936110f2565b8235815260209283019201611128565b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610914576002815414610788576002815533815260049081602052604080822090815461ffff8160501c169081156113db5764ffffffffff808260281c1680156113b3578451907f6fac17110000000000000000000000000000000000000000000000000000000082526020828a8173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115610f1057611240928992611382575b506132af565b4291161161135a579461127d7f4716cc7ed643b171db3dc2065ce09672ebba9964fe933779d9f862ae75934cba959660601c9260018601546120a9565b918061130357506112986112ea926112c592860154906128bd565b936106e85a86337f0000000000000000000000000000000000000000000000000000000000000000613b1f565b915164ffffffffff909216825233602083015260408201929092529081906060820190565b0390a16112f6336132c8565b6113006001600055565b80f35b936112c591611318846112ea95973390613a21565b81015480611327575b506106e8565b611354905a90337f0000000000000000000000000000000000000000000000000000000000000000613b1f565b38611321565b8583517fc3f53662000000000000000000000000000000000000000000000000000000008152fd5b6113a591925060203d6020116113ac575b61139d8183610252565b810190613292565b903861123a565b503d611393565b8785517f79ed4ed0000000000000000000000000000000000000000000000000000000008152fd5b8583517f0a6499c8000000000000000000000000000000000000000000000000000000008152fd5b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126109145761143b612992565b60025460ff8160a01c1660038110156114df576114b5577fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674020000000000000000000000000000000000000000176002557f3ff05a45e46337fa1cbf20996d2eeb927280bce099f37252bcca1040609604ec8180a180f35b60046040517f74ed79ae000000000000000000000000000000000000000000000000000000008152fd5b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b346103275760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610327576004356000526003602052602073ffffffffffffffffffffffffffffffffffffffff60406000205416604051908152f35b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600254600160ff8260a01c1661161f81610af8565b036117215773ffffffffffffffffffffffffffffffffffffffff1633036116f757600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790556116977fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b6116c47fffffffffffffffffffffffff000000000000000000000000000000000000000060025416600255565b6040513381527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc9080602081015b0390a1005b60046040517fafdcfb92000000000000000000000000000000000000000000000000000000008152fd5b60046040517f5e4f2826000000000000000000000000000000000000000000000000000000008152fd5b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b610180906118676102a094969593966101a08301976117c384825164ffffffffff169052565b60208181015164ffffffffff169085015260408181015161ffff169085015260608181015173ffffffffffffffffffffffffffffffffffffffff16908501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e08501526101008082015190850152610120808201519085015261014080910151908401526101608301906fffffffffffffffffffffffffffffffff169052565b01906fffffffffffffffffffffffffffffffff169052565b346103275760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103275773ffffffffffffffffffffffffffffffffffffffff6004356118cf81610917565b166000526004602052604060002060086118e7610293565b91611937815461191e64ffffffffff611909818416889064ffffffffff169052565b8260281c16602087019064ffffffffff169052565b605081901c61ffff166040860152606090811c90850152565b60018101546080840152600281015460a0840152600381015460c0840152600481015460e08401526005810154610100840152600681015461012084015260078101546101408401520154906119aa6040519283926fffffffffffffffffffffffffffffffff8260801c9216908461179d565b0390f35b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b34610327576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600435611a3c81610917565b611a44612992565b6040517fe12f3a6100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001691908381602481865afa908115610f1057600091611b52575b5080611ac857005b6040517faad3ec9600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9290921660048301526024820152908290829060449082906000905af18015610f1057611b2c57005b8161041192903d10611b4b575b611b438183610252565b8101906120bc565b503d611b39565b611b699150843d8611611b4b57611b438183610252565b38611ac0565b346103275760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757611ba6610973565b611bae610992565b6044359167ffffffffffffffff831161032757611bd160049336906004016102c7565b92611bda612992565b6fffffffffffffffffffffffffffffffff80831690611bf8826129dd565b841690611c0482612a25565b855191611c28611c1387612776565b6fffffffffffffffffffffffffffffffff1690565b8303611d105760005b838110611c6a576040517fa76e673cb694bf1ae223a1a22cac03ef92cd4d14ee9bbde1cab4fcf2939c3b2a90806116f28b8b8b846127b8565b611c7481896127a4565b518015611ce757611c86828486613360565b611c9a816000526005602052604060002090565b54611cbe5790611cb7600193926000526005602052604060002090565b5501611c31565b866040517fd1a57222000000000000000000000000000000000000000000000000000000008152fd5b856040517f3c5bdfa6000000000000000000000000000000000000000000000000000000008152fd5b60046040517f506c81a7000000000000000000000000000000000000000000000000000000008152fd5b346103275760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600435611d7581610917565b611d7d612992565b60ff60025460a01c166003811015610b02576114b5576116f27fb86c75c9bffca616b2d314cc914f7c3f1d174255b16b941c3f3ededee276d5ef91611dfc740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6002541617600255565b611e418173ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006002541617600255565b6040805133815273ffffffffffffffffffffffffffffffffffffffff909216602083015290918291820190565b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b90815180825260208080930193019160005b828110611efd575050505090565b835185529381019392810192600101611eef565b906020611f22928181520190611edd565b90565b34610327576040807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757611f5d610973565b611f65610992565b916fffffffffffffffffffffffffffffffff80921691611f84836129dd565b611f9981851694611f9486612a25565b612776565b1691611fa4836127e9565b9360009160005b858110611fc057604051806119aa8982611f11565b80611fce6001928585613360565b8552600560205285852054611fe3828a6127a4565b5201611fab565b34610327576020610a2f612009612000366109b1565b908293926120d7565b612cad565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161206a57565b61200e565b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8820191821161206a57565b9190820391821161206a57565b8181029291811591840414171561206a57565b90816020910312610327575190565b6040513d6000823e3d90fd5b91906fffffffffffffffffffffffffffffffff809116916120f7836129dd565b169061210282612a25565b8290600061210f856138e9565b9073ffffffffffffffffffffffffffffffffffffffff809616156123a8575b604051947f3e041d0f00000000000000000000000000000000000000000000000000000000865260209586816004818b88165afa908115610f1057839161237b575b506040517ff3f4370300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152908790829060249082908c165afa958615610f1057889488928598612356575b506040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909116600482015294859190829081602481015b0392165afa948515610f10576122849561227e946122749491612339575b508181106123265761226261225c61226e936122679361209c565b9661203d565b610a66565b509161206f565b90610a80565b90549060031b1c90565b906120a9565b6040517faf49f220000000000000000000000000000000000000000000000000000000008152306004820152928290849060249082907f0000000000000000000000000000000000000000000000000000000000000000165afa908115610f1057611f22936122fc93600093612307575b50506120a9565b6402540be400900490565b61231e929350803d10611b4b57611b438183610252565b9038806122f5565b505061226e61226761226260009661203d565b6123509150883d8a11611b4b57611b438183610252565b38612241565b6122239291985061237390843d8611611b4b57611b438183610252565b9790916121d3565b61239b9150873d89116123a1575b6123938183610252565b810190612a6d565b38612170565b503d612389565b7f0000000000000000000000000000000000000000000000000000000000000000935061212e565b90816040910312610327576020604051916123ea836101ab565b80516123f581610b7c565b8352015161240281610b7c565b602082015290565b90612619604060086102a094610140855161245f61242d825164ffffffffff1690565b849064ffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000825416179055565b6124af612474602083015164ffffffffff1690565b84547fffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffffff1660289190911b69ffffffffff000000000016178455565b6124fd6124c08683015161ffff1690565b84547fffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffff1660509190911b6bffff0000000000000000000016178455565b61255e612521606083015173ffffffffffffffffffffffffffffffffffffffff1690565b84546bffffffffffffffffffffffff1660609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016178455565b6080810151600184015560a0810151600284015560c0810151600384015560e08101516004840155610100810151600584015561012081015160068401550151600782015501926126026125c560208301516fffffffffffffffffffffffffffffffff1690565b85906fffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b01516fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffff0000000000000000000000000000000083549260801b169116179055565b612661612992565b60015b60038111156126a4575061269f7fc88a80305debe3a996dbe9632125859b01d5ac58be4f75123a2844a1b1cec8a7916040519182918261323a565b0390a1565b60085b60108111156126bf57506126ba90612890565b612664565b6126e46126d46126ce8461203d565b85613218565b516126de8361206f565b90613229565b5190811561274c576127426127479261270c6127026122628761203d565b5061226e8561206f565b9091907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83549160031b92831b921b1916179055565b612890565b6126a7565b60046040517f3d1527b6000000000000000000000000000000000000000000000000000000008152fd5b9060016fffffffffffffffffffffffffffffffff8093160191821161206a57565b805115610a7b5760200190565b8051821015610a7b5760209160051b010190565b606091611f2294936fffffffffffffffffffffffffffffffff80921683521660208201528160408201520190611edd565b906127f3826102af565b6128006040519182610252565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061282e82946102af565b0190602036910137565b6040519060a0820182811067ffffffffffffffff8211176101c75760405260606080836000815260006020820152600060408201526000838201520152565b60405190612884826101ab565b60006020838281520152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461206a5760010190565b9190820180921161206a57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821360011661206a57565b8181039291600013801582851316918412161761206a57565b9190916000838201938412911290801582169115161761206a57565b939060c0959897969373ffffffffffffffffffffffffffffffffffffffff6129839464ffffffffff6129759416885216602087015260e0604087015260e0860190611edd565b908482036060860152611edd565b95608083015260a08201520152565b73ffffffffffffffffffffffffffffffffffffffff6001541633036129b357565b60046040517f30cd7471000000000000000000000000000000000000000000000000000000008152fd5b60018110908115612a1a575b506129f057565b60046040517f51636152000000000000000000000000000000000000000000000000000000008152fd5b6003915011386129e9565b60088110908115612a62575b50612a3857565b60046040517faf8429cb000000000000000000000000000000000000000000000000000000008152fd5b601091501138612a31565b908160209103126103275751611f2281610917565b908160209103126103275751611f2281610b7c565b8015612ba5576040517f6a50887100000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa8015610f105761ffff91600091612b76575b501610612b4c5715612b2257565b60046040517fcd57ebca000000000000000000000000000000000000000000000000000000008152fd5b60046040517f7e0307aa000000000000000000000000000000000000000000000000000000008152fd5b612b98915060203d602011612b9e575b612b908183610252565b810190612a82565b38612b14565b503d612b86565b60046040517f5ced2d51000000000000000000000000000000000000000000000000000000008152fd5b612bd557565b60046040517f9ad3d189000000000000000000000000000000000000000000000000000000008152fd5b600013908115612c3b575b50612c1157565b60046040517fb81df4da000000000000000000000000000000000000000000000000000000008152fd5b600091501338612c0a565b90604051906020820192835260408201526000606082015260608152612c6b81610236565b519020600052600560205260406000205415612c8357565b60046040517f3c5bdfa6000000000000000000000000000000000000000000000000000000008152fd5b6040517f91c4508b00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff9092166024830152612710900491906020818060448101038173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115610f1057600091612d6d575b5082811080612d64575b612d605750565b9150565b50801515612d59565b612d86915060203d602011611b4b57611b438183610252565b38612d4f565b90816020910312610327575180151581036103275790565b602073ffffffffffffffffffffffffffffffffffffffff612dc66004936138e9565b16604051928380927f5c975abb0000000000000000000000000000000000000000000000000000000082525afa908115610f1057600091612e33575b50612e0957565b60046040517feaa69c55000000000000000000000000000000000000000000000000000000008152fd5b612e55915060203d602011612e5b575b612e4d8183610252565b810190612d8c565b38612e02565b503d612e43565b91908260c0910312610327578151612e7981610917565b91602081015167ffffffffffffffff811681036103275791604082015163ffffffff8116810361032757916060810151612eb281610b7c565b9160808201517dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811681036103275760a09092015190565b73ffffffffffffffffffffffffffffffffffffffff6040517fa5d37e8d00000000000000000000000000000000000000000000000000000000815260c081600481857f0000000000000000000000000000000000000000000000000000000000000000165afa908115610f10576000918280938192829483916130ae575b506020949596600091612fdb604051998a97889687947f5d3b1d300000000000000000000000000000000000000000000000000000000086526004860190949363ffffffff9061ffff60019567ffffffffffffffff60809660a087019a87521660208601521660408401521660608201520152565b0393165af1918215610f105760009261306d575b507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff906130698361302933916000526003602052604060002090565b9073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b1691565b7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9192506130a79060203d602011611b4b57611b438183610252565b9190612fef565b9250505060009350602092506130dc915060c03d60c0116130ea575b6130d48183610252565b810190612e62565b929650949093929190612f66565b503d6130ca565b91929173ffffffffffffffffffffffffffffffffffffffff80821661315657505061311f90613124936120a9565b6128bd565b340361312c57565b60046040517fe3057cf1000000000000000000000000000000000000000000000000000000008152fd5b919092340361312c5761318b917f000000000000000000000000000000000000000000000000000000000000000016936120a9565b823b15610327576040517fda3e8ce400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921660048301523360248301523060448301526064820152906000908290818381608481015b03925af18015610f10576132055750565b806132126102a0926101cc565b80610801565b906003811015610a7b5760051b0190565b906009811015610a7b5760051b0190565b91906103608301926000916000915b600383106132575750505050565b815184825b6009821061327b57505050602061012060019201920192019190613249565b60019083518152602080910193019101909161325c565b90816020910312610327575164ffffffffff811681036103275790565b91909164ffffffffff8080941691160191821161206a57565b6102a090610e58604051916132dc836101e0565b60008084528060208501528060408501528060608501528060808501528060a08501528060c08501528060e08501528061010085015280610120850152806101408501526040519361332d856101fd565b8452806020850152604084015273ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b916040519160208301938452604083015260608201526060815261338381610236565b51902090565b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91821660248201526020816044817f000000000000000000000000000000000000000000000000000000000000000086165afa908115610f1057600091613413575b5016151590565b61342c915060203d6020116123a1576123938183610252565b3861340c565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed464ffffffffff8092160181811161206a57604051907f6fac171100000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115610f10576134e29260009261138257506132af565b4291161190565b60009291600181036134fa57509150565b6002810361351f575090915060038102908082046003149015171561206a5760011c90565b6003146135295750565b8091925060011b908082046002149015171561206a5790565b91908215159283613571575b50821561355a57505090565b8015159250908261356a57505090565b1315905090565b82121592503861354e565b600093918480805b8382106135c357505091816135a86135a26135ae946135be96612913565b60011c90565b91613360565b6000526005602052604060002090565b549190565b909591600180841661362e57506135db6001916128ca565b925b604051613623816135f76020820194859190602083019252565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610252565b519020960190613584565b92978382019183858412911290801582169115161761206a5760019193881b17976135dd565b90939261369a9061369160018701549461368b88549361368561367f61ffff8760501c16948561209c565b896120a9565b906128bd565b956120a9565b9060601c613c45565b8161374f575b5050806136ab575050565b6136ed73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016925460601c90565b90823b15610327576040517f7c0efbd700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152906000908290818381604481016131f4565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690613792855460601c90565b91803b15610327576040517f7d1d27f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff93841660048201526024810194909452911660448301526000908290606490829084905af18015610f105761380b575b806136a0565b80613212613818926101cc565b38613805565b806138265750565b604051907fcaf7564a00000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115610f10576102a0926000926138c8575b505a917f0000000000000000000000000000000000000000000000000000000000000000613b1f565b6138e291925060203d6020116123a1576123938183610252565b903861389f565b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff918216602482015291906020836044817f000000000000000000000000000000000000000000000000000000000000000085165afa928315610f10576000936139a2575b5082161561397857565b60046040517f4daebedc000000000000000000000000000000000000000000000000000000008152fd5b6139bc91935060203d6020116123a1576123938183610252565b913861396e565b3d15613a1c573d9067ffffffffffffffff82116101c75760405191613a1060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610252565b82523d6000602084013e565b606090565b919091803b15613af5576040517fa9059cbb000000000000000000000000000000000000000000000000000000006020820190815273ffffffffffffffffffffffffffffffffffffffff9094166024820152604481019290925260009283928390613a8f81606481016135f7565b51925af1613a9b6139c3565b9015613acb57805180613aac575050565b81602080613ac193613ac59501019101612d8c565b1590565b613acb57565b60046040517ff1568f95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f09ee12d5000000000000000000000000000000000000000000000000000000008152fd5b613b3482849395600080809781948294f11590565b613b3f575b50505050565b73ffffffffffffffffffffffffffffffffffffffff16803b15613c4157604051937fd0e30db0000000000000000000000000000000000000000000000000000000008552838560048186865af1938415610f1057613bf895602095613c2e575b506040518096819582947fa9059cbb000000000000000000000000000000000000000000000000000000008452600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af18015610f1057613c0f575b808080613b39565b613c279060203d602011612e5b57612e4d8183610252565b5038613c07565b80613212613c3b926101cc565b38613b9f565b8280fd5b90613c4f826138e9565b9073ffffffffffffffffffffffffffffffffffffffff8316613c98576102a092505a917f0000000000000000000000000000000000000000000000000000000000000000613b1f565b906102a092613a2156fea2646970667358221220a7af9a9baee6615a78352ccae6e40456aab2510edebf834284f88b6f0eafd52464736f6c63430008180033000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab000000000000000000000000430000000000000000000000000000000000000400000000000000000000000095c68c52bb12a43069973fdcd88e4e93d2142f10000000000000000000000000430000000000000000000000000000000000000200000000000000000000000043000000000000000000000000000000000000030000000000000000000000002c64e6ee1dd9fc2a0db6a6b1aa2c3f163c7a2c780000000000000000000000002536fe9ab3f511540f2f9e2ec2a805005c3dd8000000000000000000000000004066b9bd584b5fa88897194dabe3a37883ac35f7
Deployed Bytecode
0x6080604052600436101561001257600080fd5b60003560e01c80631fe543e31461017757806323452b9c14610172578063239bcf501461016d578063285b9931146101685780632bb5a9e6146101635780633ccbad311461015e5780633e567539146101595780634b144d9414610154578063590e1ae31461014f5780635b6ac0111461014a5780635cb6dfff146101455780636f64c4c4146101405780637200b8291461013b5780637762df251461013657806379131a19146101315780638da5cb5b1461012c578063999927df14610127578063baebb0ee14610122578063c0b6f5611461011d578063f01f7ce414610118578063f0eb5cc0146101135763f8569e241461010e57600080fd5b611fea565b611f25565b611e6e565b611d3a565b611b6f565b611a00565b6119ae565b61187f565b61174b565b6115db565b61156c565b61150c565b611403565b611154565b61109b565b610f69565b610b88565b610b31565b610a90565b610a16565b61080c565b61032c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff8211176101c757604052565b61017c565b67ffffffffffffffff81116101c757604052565b610160810190811067ffffffffffffffff8211176101c757604052565b6060810190811067ffffffffffffffff8211176101c757604052565b610120810190811067ffffffffffffffff8211176101c757604052565b6080810190811067ffffffffffffffff8211176101c757604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176101c757604052565b604051906102a0826101e0565b565b604051906102a0826101fd565b67ffffffffffffffff81116101c75760051b60200190565b9080601f830112156103275760209082356102e1816102af565b936102ef6040519586610252565b81855260208086019260051b82010192831161032757602001905b828210610318575050505090565b8135815290830190830161030a565b600080fd5b346103275760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103275760043560243567ffffffffffffffff81116103275761037e9036906004016102c7565b73ffffffffffffffffffffffffffffffffffffffff917f00000000000000000000000095c68c52bb12a43069973fdcd88e4e93d2142f1083811633036107b257506002600054146107885760026000556103ff6103e5826000526003602052604060002090565b5473ffffffffffffffffffffffffffffffffffffffff1690565b928316610413575b6104116001600055565b005b61043d8373ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b90815461044c8160601c613389565b908161076e575b5080610761575b610465575b50610407565b61047c6104a4916000526003602052604060002090565b7fffffffffffffffffffffffff00000000000000000000000000000000000000008154169055565b6104ac612838565b926104de6104ca6104c3845461ffff9060501c1690565b61ffff1690565b936104d4856127e9565b6080870152612797565b5160208501526104ed836127e9565b906007830154936105166008850154956fffffffffffffffffffffffffffffffff8716906134e9565b9461051f612877565b92600186015495600281015460038201546006830154955b808c5110156107215761054f60408d01518385613542565b61068b578a8c8b60208201518960801c906fffffffffffffffffffffffffffffffff8b169161057d9261357c565b94919390928c61058d85836120a9565b90610597916120a9565b6305f5e10090049384936105ab82846120a9565b906105b5916120a9565b6305f5e1009004946105c88692846120a9565b6127109004906105d79161209c565b906105e19161209c565b608083015183516105f1916127a4565b5260808201518251610602916127a4565b51606083015190610612916128bd565b606083015260808201518251610627916127a4565b5190610632916128fa565b60408201519061064191612913565b90604001528b5190610652916128bd565b8b5260208b015190610663916128bd565b60208b01528d51610674908c6127a4565b5260208d01526106848c51612890565b8c52610537565b5050508197507fd3057f299a2918324f2f221be0aa7b40f5fce02abeb8c60a83cd3baa9d550097965061071093506107189894919592506106f3905b6106db86516060880151855191848b613654565b6106e8600482015461381e565b5464ffffffffff1690565b93608081015190519060208351930151936040519788978861292f565b0390a16132c8565b3880808061045f565b5050508197507fd3057f299a2918324f2f221be0aa7b40f5fce02abeb8c60a83cd3baa9d550097965061071093506107189894919592506106f3906106c7565b506005820154811461045a565b610782915060281c64ffffffffff16613432565b38610453565b60046040517f1bbee726000000000000000000000000000000000000000000000000000000008152fd5b6040517f1cf993f400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff919091166024820152604490fd5b600091031261032757565b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261091457610844612992565b60025460ff8160a01c1661085781610af8565b80156108ea5780610869600192610af8565b146108bf575b507fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff600254166002557f8eca980489e87f7dba4f26917aa4bfc906eb3f2b4f7b4b9fd0ff2b8bb3e21ae38180a180f35b7fffffffffffffffffffffffff0000000000000000000000000000000000000000166002553861086f565b60046040517fccf69db7000000000000000000000000000000000000000000000000000000008152fd5b80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361032757565b60a435906fffffffffffffffffffffffffffffffff8216820361032757565b60c435906fffffffffffffffffffffffffffffffff8216820361032757565b600435906fffffffffffffffffffffffffffffffff8216820361032757565b602435906fffffffffffffffffffffffffffffffff8216820361032757565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6060910112610327576004356109e781610917565b906fffffffffffffffffffffffffffffffff906024358281168103610327579160443590811681036103275790565b34610327576020610a2f610a29366109b1565b916120d7565b604051908152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6003811015610a7b5760090260060190600090565b610a37565b6009821015610a7b570190600090565b346103275760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610327576024356004356003811015610327576009821015610327576020916009610ae99202600601610a80565b90549060031b1c604051908152f35b60031115610b0257565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103275760ff60025460a01c166040516003821015610b02576020918152f35b61ffff81160361032757565b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600435610bbe81610b7c565b60243560443591610bce83610917565b606435608435610bdc610935565b610be4610954565b9160026000541461078857600260005561ffff9384861697610c06888a612a97565b610c49610c446104c3610c393373ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b5460501c61ffff1690565b612bcf565b610c538383612bff565b610c726fffffffffffffffffffffffffffffffff808716908616612c46565b610c7c89896120a9565b610c878686846120d7565b808211610f3f5782610c9891612cad565b11610f15578795610ca882612da4565b610cbc610cb3612ee8565b98819c856130f1565b604080517fdd8bd8540000000000000000000000000000000000000000000000000000000081523060048201529790919082896024817f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d73ffffffffffffffffffffffffffffffffffffffff165afa918215610f10578a81610e5d947f500b76c50bb3487d6c0addddaa452c56bfecb1202117bb7852c34c8acd6b78979f948f95610ed49e600093610ee1575b5064ffffffffff96610dc4610d8f6020610d85875161ffff1690565b96015161ffff1690565b96610dba610d9b610293565b64ffffffffff438d161681529a421660208c019064ffffffffff169052565b61ffff16898c0152565b73ffffffffffffffffffffffffffffffffffffffff8b16606089015260808801528a60a08801528b60c088015260e08701526101008601521661012084015216610140820152610e126102a2565b9081526fffffffffffffffffffffffffffffffff871660208201526fffffffffffffffffffffffffffffffff88168184015233600090815260046020526040902061240a565b61240a565b5197889733438a9795929361ffff61010098959b9a96929b6101208b019c8b5273ffffffffffffffffffffffffffffffffffffffff80951660208c01521660408a0152606089015216608087015260a086015260c08501526fffffffffffffffffffffffffffffffff80921660e085015216910152565b0390a16104116001600055565b610f02919350893d8b11610f09575b610efa8183610252565b8101906123d0565b9138610d69565b503d610ef0565b6120cb565b60046040517f367b6990000000000000000000000000000000000000000000000000000000008152fd5b60046040517ffb167496000000000000000000000000000000000000000000000000000000008152fd5b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261091457610fa1612992565b60ff60025460a01c16600381101561106e5760020361104457610fe77fffffffffffffffffffffffff000000000000000000000000000000000000000060015416600155565b6110147fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b604051600081527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc90602090a180f35b60046040517f045c5122000000000000000000000000000000000000000000000000000000008152fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b34610327576103607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757366023121561032757604080516110e1816101fd565b809161036490368211610327576004935b8285106111025761041184612659565b36601f8601121561032757815161111881610219565b8061012087013681116103275787915b81831061114457505050816020916101209352019401936110f2565b8235815260209283019201611128565b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610914576002815414610788576002815533815260049081602052604080822090815461ffff8160501c169081156113db5764ffffffffff808260281c1680156113b3578451907f6fac17110000000000000000000000000000000000000000000000000000000082526020828a8173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610f1057611240928992611382575b506132af565b4291161161135a579461127d7f4716cc7ed643b171db3dc2065ce09672ebba9964fe933779d9f862ae75934cba959660601c9260018601546120a9565b918061130357506112986112ea926112c592860154906128bd565b936106e85a86337f0000000000000000000000004300000000000000000000000000000000000004613b1f565b915164ffffffffff909216825233602083015260408201929092529081906060820190565b0390a16112f6336132c8565b6113006001600055565b80f35b936112c591611318846112ea95973390613a21565b81015480611327575b506106e8565b611354905a90337f0000000000000000000000004300000000000000000000000000000000000004613b1f565b38611321565b8583517fc3f53662000000000000000000000000000000000000000000000000000000008152fd5b6113a591925060203d6020116113ac575b61139d8183610252565b810190613292565b903861123a565b503d611393565b8785517f79ed4ed0000000000000000000000000000000000000000000000000000000008152fd5b8583517f0a6499c8000000000000000000000000000000000000000000000000000000008152fd5b34610327576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126109145761143b612992565b60025460ff8160a01c1660038110156114df576114b5577fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674020000000000000000000000000000000000000000176002557f3ff05a45e46337fa1cbf20996d2eeb927280bce099f37252bcca1040609604ec8180a180f35b60046040517f74ed79ae000000000000000000000000000000000000000000000000000000008152fd5b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b346103275760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610327576004356000526003602052602073ffffffffffffffffffffffffffffffffffffffff60406000205416604051908152f35b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602060405173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d168152f35b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600254600160ff8260a01c1661161f81610af8565b036117215773ffffffffffffffffffffffffffffffffffffffff1633036116f757600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790556116977fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff60025416600255565b6116c47fffffffffffffffffffffffff000000000000000000000000000000000000000060025416600255565b6040513381527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc9080602081015b0390a1005b60046040517fafdcfb92000000000000000000000000000000000000000000000000000000008152fd5b60046040517f5e4f2826000000000000000000000000000000000000000000000000000000008152fd5b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b610180906118676102a094969593966101a08301976117c384825164ffffffffff169052565b60208181015164ffffffffff169085015260408181015161ffff169085015260608181015173ffffffffffffffffffffffffffffffffffffffff16908501526080810151608085015260a081015160a085015260c081015160c085015260e081015160e08501526101008082015190850152610120808201519085015261014080910151908401526101608301906fffffffffffffffffffffffffffffffff169052565b01906fffffffffffffffffffffffffffffffff169052565b346103275760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103275773ffffffffffffffffffffffffffffffffffffffff6004356118cf81610917565b166000526004602052604060002060086118e7610293565b91611937815461191e64ffffffffff611909818416889064ffffffffff169052565b8260281c16602087019064ffffffffff169052565b605081901c61ffff166040860152606090811c90850152565b60018101546080840152600281015460a0840152600381015460c0840152600481015460e08401526005810154610100840152600681015461012084015260078101546101408401520154906119aa6040519283926fffffffffffffffffffffffffffffffff8260801c9216908461179d565b0390f35b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b34610327576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600435611a3c81610917565b611a44612992565b6040517fe12f3a6100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000043000000000000000000000000000000000000031691908381602481865afa908115610f1057600091611b52575b5080611ac857005b6040517faad3ec9600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9290921660048301526024820152908290829060449082906000905af18015610f1057611b2c57005b8161041192903d10611b4b575b611b438183610252565b8101906120bc565b503d611b39565b611b699150843d8611611b4b57611b438183610252565b38611ac0565b346103275760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757611ba6610973565b611bae610992565b6044359167ffffffffffffffff831161032757611bd160049336906004016102c7565b92611bda612992565b6fffffffffffffffffffffffffffffffff80831690611bf8826129dd565b841690611c0482612a25565b855191611c28611c1387612776565b6fffffffffffffffffffffffffffffffff1690565b8303611d105760005b838110611c6a576040517fa76e673cb694bf1ae223a1a22cac03ef92cd4d14ee9bbde1cab4fcf2939c3b2a90806116f28b8b8b846127b8565b611c7481896127a4565b518015611ce757611c86828486613360565b611c9a816000526005602052604060002090565b54611cbe5790611cb7600193926000526005602052604060002090565b5501611c31565b866040517fd1a57222000000000000000000000000000000000000000000000000000000008152fd5b856040517f3c5bdfa6000000000000000000000000000000000000000000000000000000008152fd5b60046040517f506c81a7000000000000000000000000000000000000000000000000000000008152fd5b346103275760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757600435611d7581610917565b611d7d612992565b60ff60025460a01c166003811015610b02576114b5576116f27fb86c75c9bffca616b2d314cc914f7c3f1d174255b16b941c3f3ededee276d5ef91611dfc740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6002541617600255565b611e418173ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000006002541617600255565b6040805133815273ffffffffffffffffffffffffffffffffffffffff909216602083015290918291820190565b346103275760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757602060405173ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab168152f35b90815180825260208080930193019160005b828110611efd575050505090565b835185529381019392810192600101611eef565b906020611f22928181520190611edd565b90565b34610327576040807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261032757611f5d610973565b611f65610992565b916fffffffffffffffffffffffffffffffff80921691611f84836129dd565b611f9981851694611f9486612a25565b612776565b1691611fa4836127e9565b9360009160005b858110611fc057604051806119aa8982611f11565b80611fce6001928585613360565b8552600560205285852054611fe3828a6127a4565b5201611fab565b34610327576020610a2f612009612000366109b1565b908293926120d7565b612cad565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161206a57565b61200e565b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8820191821161206a57565b9190820391821161206a57565b8181029291811591840414171561206a57565b90816020910312610327575190565b6040513d6000823e3d90fd5b91906fffffffffffffffffffffffffffffffff809116916120f7836129dd565b169061210282612a25565b8290600061210f856138e9565b9073ffffffffffffffffffffffffffffffffffffffff809616156123a8575b604051947f3e041d0f00000000000000000000000000000000000000000000000000000000865260209586816004818b88165afa908115610f1057839161237b575b506040517ff3f4370300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152908790829060249082908c165afa958615610f1057889488928598612356575b506040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909116600482015294859190829081602481015b0392165afa948515610f10576122849561227e946122749491612339575b508181106123265761226261225c61226e936122679361209c565b9661203d565b610a66565b509161206f565b90610a80565b90549060031b1c90565b906120a9565b6040517faf49f220000000000000000000000000000000000000000000000000000000008152306004820152928290849060249082907f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610f1057611f22936122fc93600093612307575b50506120a9565b6402540be400900490565b61231e929350803d10611b4b57611b438183610252565b9038806122f5565b505061226e61226761226260009661203d565b6123509150883d8a11611b4b57611b438183610252565b38612241565b6122239291985061237390843d8611611b4b57611b438183610252565b9790916121d3565b61239b9150873d89116123a1575b6123938183610252565b810190612a6d565b38612170565b503d612389565b7f0000000000000000000000004300000000000000000000000000000000000004935061212e565b90816040910312610327576020604051916123ea836101ab565b80516123f581610b7c565b8352015161240281610b7c565b602082015290565b90612619604060086102a094610140855161245f61242d825164ffffffffff1690565b849064ffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000825416179055565b6124af612474602083015164ffffffffff1690565b84547fffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffffff1660289190911b69ffffffffff000000000016178455565b6124fd6124c08683015161ffff1690565b84547fffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffff1660509190911b6bffff0000000000000000000016178455565b61255e612521606083015173ffffffffffffffffffffffffffffffffffffffff1690565b84546bffffffffffffffffffffffff1660609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016178455565b6080810151600184015560a0810151600284015560c0810151600384015560e08101516004840155610100810151600584015561012081015160068401550151600782015501926126026125c560208301516fffffffffffffffffffffffffffffffff1690565b85906fffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b01516fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffff0000000000000000000000000000000083549260801b169116179055565b612661612992565b60015b60038111156126a4575061269f7fc88a80305debe3a996dbe9632125859b01d5ac58be4f75123a2844a1b1cec8a7916040519182918261323a565b0390a1565b60085b60108111156126bf57506126ba90612890565b612664565b6126e46126d46126ce8461203d565b85613218565b516126de8361206f565b90613229565b5190811561274c576127426127479261270c6127026122628761203d565b5061226e8561206f565b9091907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83549160031b92831b921b1916179055565b612890565b6126a7565b60046040517f3d1527b6000000000000000000000000000000000000000000000000000000008152fd5b9060016fffffffffffffffffffffffffffffffff8093160191821161206a57565b805115610a7b5760200190565b8051821015610a7b5760209160051b010190565b606091611f2294936fffffffffffffffffffffffffffffffff80921683521660208201528160408201520190611edd565b906127f3826102af565b6128006040519182610252565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061282e82946102af565b0190602036910137565b6040519060a0820182811067ffffffffffffffff8211176101c75760405260606080836000815260006020820152600060408201526000838201520152565b60405190612884826101ab565b60006020838281520152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461206a5760010190565b9190820180921161206a57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821360011661206a57565b8181039291600013801582851316918412161761206a57565b9190916000838201938412911290801582169115161761206a57565b939060c0959897969373ffffffffffffffffffffffffffffffffffffffff6129839464ffffffffff6129759416885216602087015260e0604087015260e0860190611edd565b908482036060860152611edd565b95608083015260a08201520152565b73ffffffffffffffffffffffffffffffffffffffff6001541633036129b357565b60046040517f30cd7471000000000000000000000000000000000000000000000000000000008152fd5b60018110908115612a1a575b506129f057565b60046040517f51636152000000000000000000000000000000000000000000000000000000008152fd5b6003915011386129e9565b60088110908115612a62575b50612a3857565b60046040517faf8429cb000000000000000000000000000000000000000000000000000000008152fd5b601091501138612a31565b908160209103126103275751611f2281610917565b908160209103126103275751611f2281610b7c565b8015612ba5576040517f6a50887100000000000000000000000000000000000000000000000000000000815260208160048173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa8015610f105761ffff91600091612b76575b501610612b4c5715612b2257565b60046040517fcd57ebca000000000000000000000000000000000000000000000000000000008152fd5b60046040517f7e0307aa000000000000000000000000000000000000000000000000000000008152fd5b612b98915060203d602011612b9e575b612b908183610252565b810190612a82565b38612b14565b503d612b86565b60046040517f5ced2d51000000000000000000000000000000000000000000000000000000008152fd5b612bd557565b60046040517f9ad3d189000000000000000000000000000000000000000000000000000000008152fd5b600013908115612c3b575b50612c1157565b60046040517fb81df4da000000000000000000000000000000000000000000000000000000008152fd5b600091501338612c0a565b90604051906020820192835260408201526000606082015260608152612c6b81610236565b519020600052600560205260406000205415612c8357565b60046040517f3c5bdfa6000000000000000000000000000000000000000000000000000000008152fd5b6040517f91c4508b00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff9092166024830152612710900491906020818060448101038173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610f1057600091612d6d575b5082811080612d64575b612d605750565b9150565b50801515612d59565b612d86915060203d602011611b4b57611b438183610252565b38612d4f565b90816020910312610327575180151581036103275790565b602073ffffffffffffffffffffffffffffffffffffffff612dc66004936138e9565b16604051928380927f5c975abb0000000000000000000000000000000000000000000000000000000082525afa908115610f1057600091612e33575b50612e0957565b60046040517feaa69c55000000000000000000000000000000000000000000000000000000008152fd5b612e55915060203d602011612e5b575b612e4d8183610252565b810190612d8c565b38612e02565b503d612e43565b91908260c0910312610327578151612e7981610917565b91602081015167ffffffffffffffff811681036103275791604082015163ffffffff8116810361032757916060810151612eb281610b7c565b9160808201517dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811681036103275760a09092015190565b73ffffffffffffffffffffffffffffffffffffffff6040517fa5d37e8d00000000000000000000000000000000000000000000000000000000815260c081600481857f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610f10576000918280938192829483916130ae575b506020949596600091612fdb604051998a97889687947f5d3b1d300000000000000000000000000000000000000000000000000000000086526004860190949363ffffffff9061ffff60019567ffffffffffffffff60809660a087019a87521660208601521660408401521660608201520152565b0393165af1918215610f105760009261306d575b507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff906130698361302933916000526003602052604060002090565b9073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b1691565b7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9192506130a79060203d602011611b4b57611b438183610252565b9190612fef565b9250505060009350602092506130dc915060c03d60c0116130ea575b6130d48183610252565b810190612e62565b929650949093929190612f66565b503d6130ca565b91929173ffffffffffffffffffffffffffffffffffffffff80821661315657505061311f90613124936120a9565b6128bd565b340361312c57565b60046040517fe3057cf1000000000000000000000000000000000000000000000000000000008152fd5b919092340361312c5761318b917f00000000000000000000000000000000007fe8d7666bb0da2a5d13f72b8dabab16936120a9565b823b15610327576040517fda3e8ce400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921660048301523360248301523060448301526064820152906000908290818381608481015b03925af18015610f10576132055750565b806132126102a0926101cc565b80610801565b906003811015610a7b5760051b0190565b906009811015610a7b5760051b0190565b91906103608301926000916000915b600383106132575750505050565b815184825b6009821061327b57505050602061012060019201920192019190613249565b60019083518152602080910193019101909161325c565b90816020910312610327575164ffffffffff811681036103275790565b91909164ffffffffff8080941691160191821161206a57565b6102a090610e58604051916132dc836101e0565b60008084528060208501528060408501528060608501528060808501528060a08501528060c08501528060e08501528061010085015280610120850152806101408501526040519361332d856101fd565b8452806020850152604084015273ffffffffffffffffffffffffffffffffffffffff166000526004602052604060002090565b916040519160208301938452604083015260608201526060815261338381610236565b51902090565b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91821660248201526020816044817f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d86165afa908115610f1057600091613413575b5016151590565b61342c915060203d6020116123a1576123938183610252565b3861340c565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed464ffffffffff8092160181811161206a57604051907f6fac171100000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610f10576134e29260009261138257506132af565b4291161190565b60009291600181036134fa57509150565b6002810361351f575090915060038102908082046003149015171561206a5760011c90565b6003146135295750565b8091925060011b908082046002149015171561206a5790565b91908215159283613571575b50821561355a57505090565b8015159250908261356a57505090565b1315905090565b82121592503861354e565b600093918480805b8382106135c357505091816135a86135a26135ae946135be96612913565b60011c90565b91613360565b6000526005602052604060002090565b549190565b909591600180841661362e57506135db6001916128ca565b925b604051613623816135f76020820194859190602083019252565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610252565b519020960190613584565b92978382019183858412911290801582169115161761206a5760019193881b17976135dd565b90939261369a9061369160018701549461368b88549361368561367f61ffff8760501c16948561209c565b896120a9565b906128bd565b956120a9565b9060601c613c45565b8161374f575b5050806136ab575050565b6136ed73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d16925460601c90565b90823b15610327576040517f7c0efbd700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152906000908290818381604481016131f4565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d1690613792855460601c90565b91803b15610327576040517f7d1d27f200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff93841660048201526024810194909452911660448301526000908290606490829084905af18015610f105761380b575b806136a0565b80613212613818926101cc565b38613805565b806138265750565b604051907fcaf7564a00000000000000000000000000000000000000000000000000000000825260208260048173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d165afa908115610f10576102a0926000926138c8575b505a917f0000000000000000000000004300000000000000000000000000000000000004613b1f565b6138e291925060203d6020116123a1576123938183610252565b903861389f565b6040517f60931da800000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff918216602482015291906020836044817f000000000000000000000000572a1fa9e45c2ec681aba11b9fdb829a5ba9e50d85165afa928315610f10576000936139a2575b5082161561397857565b60046040517f4daebedc000000000000000000000000000000000000000000000000000000008152fd5b6139bc91935060203d6020116123a1576123938183610252565b913861396e565b3d15613a1c573d9067ffffffffffffffff82116101c75760405191613a1060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610252565b82523d6000602084013e565b606090565b919091803b15613af5576040517fa9059cbb000000000000000000000000000000000000000000000000000000006020820190815273ffffffffffffffffffffffffffffffffffffffff9094166024820152604481019290925260009283928390613a8f81606481016135f7565b51925af1613a9b6139c3565b9015613acb57805180613aac575050565b81602080613ac193613ac59501019101612d8c565b1590565b613acb57565b60046040517ff1568f95000000000000000000000000000000000000000000000000000000008152fd5b60046040517f09ee12d5000000000000000000000000000000000000000000000000000000008152fd5b613b3482849395600080809781948294f11590565b613b3f575b50505050565b73ffffffffffffffffffffffffffffffffffffffff16803b15613c4157604051937fd0e30db0000000000000000000000000000000000000000000000000000000008552838560048186865af1938415610f1057613bf895602095613c2e575b506040518096819582947fa9059cbb000000000000000000000000000000000000000000000000000000008452600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af18015610f1057613c0f575b808080613b39565b613c279060203d602011612e5b57612e4d8183610252565b5038613c07565b80613212613c3b926101cc565b38613b9f565b8280fd5b90613c4f826138e9565b9073ffffffffffffffffffffffffffffffffffffffff8316613c98576102a092505a917f0000000000000000000000004300000000000000000000000000000000000004613b1f565b906102a092613a2156fea2646970667358221220a7af9a9baee6615a78352ccae6e40456aab2510edebf834284f88b6f0eafd52464736f6c63430008180033
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
Loading...
Loading
[ 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.