diff --git a/packages/hardhat/contracts/Tournament.sol b/packages/hardhat/contracts/Tournament.sol index a0cd7c6..ef09aa0 100644 --- a/packages/hardhat/contracts/Tournament.sol +++ b/packages/hardhat/contracts/Tournament.sol @@ -4,9 +4,11 @@ pragma solidity >=0.8.0 <0.9.0; // import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {VRFCoordinatorV2Interface} from "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; -import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol"; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {VRFCoordinatorV2Interface} from "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; +//import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol"; +import "./VRFConsumerBaseV2Upgradeable.sol"; interface YearnInterface { function pricePerShare() external view returns (uint256); @@ -20,7 +22,7 @@ interface UniswapInterface { function token1() external view returns (address); // Underlying asset } -contract Tournament is VRFConsumerBaseV2{ +contract Tournament is Initializable, VRFConsumerBaseV2Upgradeable { ////////////// /// ERRORS /// @@ -30,10 +32,9 @@ contract Tournament is VRFConsumerBaseV2{ /// State Variables /// /////////////////////// - address public immutable owner; + address public owner; //TOURNAMENT INFO - // @todo could probably put all tournament info in a struct string public name; // Name of the tournament IERC20Metadata poolIncentivized; uint256 public depositAmount; // Exact Amount of LP to be deposited by the players @@ -89,9 +90,9 @@ contract Tournament is VRFConsumerBaseV2{ uint256[] public requestIds; uint256 public lastRequestId; - VRFCoordinatorV2Interface private immutable i_vrfCoordinator; - uint64 private immutable i_subscriptionId; - bytes32 private immutable i_gasLane; + VRFCoordinatorV2Interface private i_vrfCoordinator; + uint64 private i_subscriptionId; + bytes32 private i_gasLane; uint32 private gasLimit; uint8 private constant REQUEST_CONFIRMATIONS = 3; uint8 private constant NUM_WORDS = 1; @@ -141,9 +142,7 @@ contract Tournament is VRFConsumerBaseV2{ _; } - // Constructor: Called once on contract deployment - // Check packages/hardhat/deploy/00_deploy_your_contract.ts - constructor( + function initialize( address _owner, string memory _name, address _poolIncentivized, @@ -155,7 +154,7 @@ contract Tournament is VRFConsumerBaseV2{ bytes32 _gasLane, uint32 _callbackGasLimit, address _vrfCoordinatorV2 - ) VRFConsumerBaseV2(_vrfCoordinatorV2) { + ) public initializer { require(_startTime < _endTime, "Start time must be before end time"); // Defaults to current block timestamp startTime = _startTime == 0 ? uint32(block.timestamp) : _startTime; @@ -176,6 +175,7 @@ contract Tournament is VRFConsumerBaseV2{ } endTime = _endTime; //VRF + __VRFConsumerBaseV2Upgradeable_init(_vrfCoordinatorV2); i_vrfCoordinator = VRFCoordinatorV2Interface(_vrfCoordinatorV2); i_subscriptionId = _subscriptionId; i_gasLane = _gasLane; @@ -187,7 +187,7 @@ contract Tournament is VRFConsumerBaseV2{ ///////////////////////////// /** - * Function that allows anyone to stake their LP token to register in the tournament + * @notice Allows the players to stake their LP token to register for the tournament */ function stakeLPToken() public { require(!isPlayer(msg.sender), "You have already staked"); @@ -200,13 +200,14 @@ contract Tournament is VRFConsumerBaseV2{ } /** - * Function that allows anyone to unstake their LP token once the tournament is over + * @notice Allows the players to withdraw their entitled LP token amount once the tournament is over + * @dev Also updates the state of the contract to reflect the withdrawal */ function unstakeLPToken() public { require(isPlayer(msg.sender), "You have nothing to withdraw"); // Address never staked or already withdrew require(unstakingAllowed(), "Unstaking not allowed"); // Get back its deposited value of underlying assets - uint256 amount = LPTokenAmountOfPlayer(msg.sender); // corresponds to deposited underlying assets + uint256 amount = withdrawAmountFromDeposit(msg.sender); // corresponds to deposited underlying assets uint256 extraPoolPrize = (1 ether - fees) * (depositAmount - amount) / 1 ether; // How much LP token is left by the user realizedFees += depositAmount - amount - extraPoolPrize; // Add rewards from the game @@ -222,9 +223,9 @@ contract Tournament is VRFConsumerBaseV2{ } /** - * Function that allows the owner to withdraw realized fees - * Total fees will be available for withdrawal once all players have withdrawn - * Partial fees can be withdran at any time after players begun to withdraw + * @notice Allows the owner to withdraw realized fees + * @dev Total fees will be available for withdrawal once all players have withdrawn + * @dev Partial fees can be withdran at any time after players begun to withdraw */ function withdrawFees() public onlyOwner { require(realizedFees > 0, "No fees to withdraw"); @@ -239,7 +240,8 @@ contract Tournament is VRFConsumerBaseV2{ /////////////////////////// /** - * Function that allows the player to submit a move for play against another player + * @notice Submit a move for play against another player + * @param _move is the player's move */ function playAgainstPlayer(uint8 _move) public { require(_move <= 2, "Invalid move"); @@ -250,7 +252,7 @@ contract Tournament is VRFConsumerBaseV2{ if(storedPlayer.addr != address(0)) { // A player is already waiting to be matched - _resolveGame(_move); + resolveGame(_move); } else { // No player is waiting to be matched, we store the move and wait for a player to join storedPlayer.move = _move; @@ -261,7 +263,9 @@ contract Tournament is VRFConsumerBaseV2{ } /** - * Function that allows the player to submit a move for play against Chainlink VRF + * @notice Submit a move for play against a contract + * @param _move is the player's move + * @return requestId is the requestId generated by chainlink and used to grab the game struct */ function playAgainstContract(uint8 _move) public returns(uint256 requestId) { require(_move <= 2, "Invalid move"); @@ -284,10 +288,12 @@ contract Tournament is VRFConsumerBaseV2{ // gaslane: 0x354d2f95da55398f44b7cff77da56283d9c6c829a4bdf1bbcaf2ad6a4d081f61 // vrf: 0x2eD832Ba664535e5886b75D64C46EB9a228C2610 - - // It will request a random number from the VRF - // If a request is successful, the callback function, fulfillRandomWords will be called. - // @return requestId is the requestId generated by chainlink + /** + * @notice Requests a random number from the VRF + * @dev If a request is successful, the callback function, fulfillRandomWords will be called. + * @param _playerMove is the player's move + * @param _player is the player's address + */ function _requestRandomWords(uint8 _playerMove, address _player) internal returns (uint256 requestId) { // Will revert if subscription is not set and funded. @@ -314,11 +320,11 @@ contract Tournament is VRFConsumerBaseV2{ lastRequestId = requestId; } - - /** - * This is the function that Chainlink VRF node - * calls to play the game. - */ + /** + * @notice Handle VRF callback + * @param requestId is the requestId generated by chainlink and used to grab the game struct + * @param randomWords is the random number generated by the VRF + */ function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { // check number is ready require(contractGameRequestId[requestId].exists, "request not found"); @@ -344,8 +350,12 @@ contract Tournament is VRFConsumerBaseV2{ */ function resolveBatch() public { } - - function _resolveGame(uint8 _move) internal { + + /** + * @notice Resolves the game against another player + * @param _move The move the player made + */ + function resolveGame(uint8 _move) internal { if(_move == storedPlayer.move) { // Draw updateScore(msg.sender, 1); @@ -369,11 +379,9 @@ contract Tournament is VRFConsumerBaseV2{ } /** + * @notice Resolves the game against the contract (VRF) * @param requestId is the requestId generated by chainlink and used to grab the game struct - * @dev this resolves a game aganist the contract */ - //@todo make one function that resolves both pvp and vrf games - //@todo make it only callable by VRF fulfillRandomwords function resolveVrfGame(uint256 requestId) internal { ContractGame storage game = contractGameRequestId[requestId]; if(game.playerMove == game.vrfMove){ @@ -396,11 +404,11 @@ contract Tournament is VRFConsumerBaseV2{ } /** - * Function that updates the player score by adding the points + * @notice Updates the player score by adding points + * @dev Should be called in any case. Also updates player's rank and streak * @param _player is the address of the player * @param _points 0 = lost, 1 = draw, 2 = won */ - // @note MUST CALL AFTER RESOLVING GAME function updateScore(address _player, uint8 _points) internal { if(_points == 0) { playersMap[_player].streak = 0; @@ -456,33 +464,42 @@ contract Tournament is VRFConsumerBaseV2{ } /** - * Function that returns the current price per share from the LP token contract + * @notice Returns the current price per share of the LP token + * @dev Get function for Yearn, while for Uniswap we need to compute k / supply + * @return pPS The current price per share */ - function getPricePerShare() public view returns(uint256) { + function getPricePerShare() public view returns(uint256 pPS) { if(Protocol.Yearn == protocol) { YearnInterface yearn = YearnInterface(address(poolIncentivized)); - return yearn.pricePerShare(); + pPS = yearn.pricePerShare(); } else { // Uniswap UniswapInterface uniswap = UniswapInterface(address(poolIncentivized)); (uint112 res0, uint112 res1, ) = uniswap.getReserves(); uint256 supply = uniswap.totalSupply(); - return uint256(res0) * uint256(res1) / supply; + pPS = uint256(res0) * uint256(res1) / supply; } } /** - * Function that returns the current amount of LP token entitled to the player on withdrawal (before adding earned prizes) + * @notice Returns the current amount of LP token entitled to the player on withdrawal + * @dev Ensures that the player will get the same value of underlying assets that he deposited. Earnings not included + * @param _player The player address + * @return amount The amount of LP token the player would receive if he withdraws now */ - function LPTokenAmountOfPlayer(address _player) public view returns (uint256) { + function withdrawAmountFromDeposit(address _player) public view returns (uint256 amount) { uint256 pPS = getPricePerShare(); - if(playersMap[_player].depositPricePerShare == 0) { return 0; } // User aleady withdrew + if(playersMap[_player].depositPricePerShare == 0) return 0; // User aleady withdrew // We prevent user to receive more LP than deposited in exeptional case where pPS disminushes pPS = (pPS < playersMap[_player].depositPricePerShare) ? playersMap[_player].depositPricePerShare : pPS; - return depositAmount * playersMap[_player].depositPricePerShare / pPS; + amount = depositAmount * playersMap[_player].depositPricePerShare / pPS; } /** - * Function that returns the player's rank and how many players share this rank + * @notice Returns the rank of the player + * @dev 50% shared for 1st rank, 25% shared for 2nd rank, etc. 1 ether = 100% + * @param _player The player address + * @return rank The player's rank + * @return split The number of players sharing the same rank */ function getRank(address _player) public view returns (uint16 rank, uint16 split) { if(!isPlayer(_player)) return (0, 0); @@ -502,150 +519,226 @@ contract Tournament is VRFConsumerBaseV2{ } /** - * Function that returns the player's prize share (50% shared for 1st rank, 25% shared for 2nd rank, etc) - * 1 ether = 100% + * @notice Returns the share of the pool prize earned by the player + * @dev 50% shared for 1st rank, 25% shared for 2nd rank, etc. 1 ether = 100% + * @param _player The player address + * @return share The player's share */ - function getPrizeShare(address _player) public view returns (uint64) { + function getPrizeShare(address _player) public view returns (uint64 share) { // TODO: how to manage rewards if the number of different ranks is low? (uint256 rank, uint256 split) = getRank(_player); if(split == 0) return 0; // Not a player = no share uint8 multiplier = (nbRanks == rank) ? 2 : 1; // We double the allocation for the last rank so that sum of shares is 100% - return uint64((multiplier * 1 ether / (2 ** rank)) / split); + share = uint64((multiplier * 1 ether / (2 ** rank)) / split); } /** - * Function that returns the amount of LP token in the pool prize + * @notice Returns the total pool prize + * @dev The realized pool price is static while remaining pool prize is dynamic + * @return amount The pool prize amount */ - function getPoolPrize() public view returns (uint256) { - return realizedPoolPrize + getRemainingPoolPrize(); + function getPoolPrize() public view returns (uint256 amount) { + amount = realizedPoolPrize + getRemainingPoolPrize(); } /** - * Function that returns the amount of LP token remaining in the pool prize + * @notice Returns the amount of pool prize left + * @dev The number of LP tokens will be obtained from the players that did not withdraw yet + * @return amount The remaining pool prize amount */ - function getRemainingPoolPrize() public view returns (uint256) { + function getRemainingPoolPrize() public view returns (uint256 amount) { uint256 extraLP = 0; for (uint i=0; i= timeToDate(endTime); + /** + * @notice Returns if the tournament is ended + * @dev Players can only withdraw if the tournament has ended. Use unstakingAllowed() to check if unstaking is allowed + * @return ended + */ + function isEnded() public view returns (bool ended) { + ended = timeToDate(uint32(block.timestamp)) >= timeToDate(endTime); } /** - * Function that returns true if the tournament is not yet started + * @notice Returns if the tournament is not yet started + * @dev Players can only stake if the tournament is future. Use stakingAllowed() to check if staking is allowed + * @return future */ - function isFuture() public view returns (bool) { - return timeToDate(uint32(block.timestamp)) < timeToDate(startTime); + function isFuture() public view returns (bool future) { + future = timeToDate(uint32(block.timestamp)) < timeToDate(startTime); } /** - * Function that returns if the tournament is active (players are allowed to play) + * @notice Returns if the tournament is active + * @dev Players can only play if the tournament is active + * @return active */ - function isActive() public view returns (bool) { - return !isFuture() && !isEnded(); + function isActive() public view returns (bool active) { + active = !isFuture() && !isEnded(); } /** - * Function that returns true if the address deposited and did not withdraw + * @notice Returns if the player is registered in this tournament + * @dev Returns true if the player has made a deposit and has not yet withdrawn + * @param _player The player address + * @return isP */ - function isPlayer(address _player) public view returns (bool) { - return playersMap[_player].depositPricePerShare > 0; + function isPlayer(address _player) public view returns (bool isP) { + isP = playersMap[_player].depositPricePerShare > 0; } /** - * Function that returns if the player has already played today (resets at O0:OO UTC) - * + * @notice Returns if the player has already played today + * @dev Resets at O0:OO UTC + * @param _player The player address */ - //@note compaired to midnight - use to reset live per day function alreadyPlayed(address _player) public view returns (bool) { uint32 today = timeToDate(uint32(block.timestamp)); uint32 lastGame = timeToDate(playersMap[_player].lastGame); return today == lastGame; } + /** + * @notice Returns the player's score + * @param _player The player address + */ function pointsOfPlayer(address _player) public view returns (uint16) { return playersMap[_player].score; } - function stakingAllowed() public view returns (bool) { - return !isEnded(); + /** + * @notice Returns if staking is allowed + * @dev Players can stake anytime until 1 day before the end of the game. If they were able to stake at last minute, they could get a share of the pool prize without any contribution. + * @return allowed + */ + function stakingAllowed() public view returns (bool allowed) { + allowed = timeToDate(uint32(block.timestamp)) >= timeToDate(endTime - 1 days); } - function unstakingAllowed() public view returns (bool) { - return isEnded(); + /** + * @notice Returns if unstaking is allowed + * @dev Players can stake anytime after the end of the game + * @return allowed + */ + function unstakingAllowed() public view returns (bool allowed) { + allowed = isEnded(); } - function getNumberOfPlayers() public view returns (uint16) { - return uint16(players.length); + /** + * @notice Returns the number of players + * @return number Number of players + */ + function getNumberOfPlayers() public view returns (uint16 number) { + number = uint16(players.length); } - function getPlayers() public view returns (address[] memory) { - return players; + /** + * @notice Returns the list of all players + * @return arr List of players + */ + function getPlayers() public view returns (address[] memory arr) { + arr = players; } - function getPlayersAtScore(uint16 _score) public view returns (address[] memory) { + /** + * @notice Returns the list of players at a given score + * @param _score The score of the players + * @return arr List of players + */ + function getPlayersAtScore(uint16 _score) public view returns (address[] memory arr) { if(_score == 0) return new address[](0); // We don't return the list of players without any point - return scoreToPlayers[_score]; + arr = scoreToPlayers[_score]; } + /** + * @notice Returns data available on the player + * @param _player The address of the player + * @return rank of the player + * @return score of the player + * @return lastGame Last time the player played + */ function getPlayer(address _player) public view returns (uint16 rank, uint16 score, uint32 lastGame) { (rank, ) = getRank(_player); score = playersMap[_player].score; lastGame = playersMap[_player].lastGame; } - function getLPDecimals() public view returns (uint8) { - return IERC20Metadata(poolIncentivized).decimals(); + /** + * @notice Returns the number of decimals of the LP token + * @return decimals Number of decimals + */ + function getLPDecimals() public view returns (uint8 decimals) { + decimals = IERC20Metadata(poolIncentivized).decimals(); } - function getLPSymbol() public view returns (string memory) { - return IERC20Metadata(poolIncentivized).symbol(); + /** + * @notice Returns the symbol of the pool as defined in the pool contract + * @return symbol of the pool + */ + function getLPSymbol() public view returns (string memory symbol) { + symbol = IERC20Metadata(poolIncentivized).symbol(); } - function getFancySymbol() public view returns (string memory) { + /** + * @notice Returns the symbol of the pool + * @dev If the pool is Uniswapn it adds the symbol of the underlying tokens to UNI-V2 + * @return symbol "fancy" symbol of the pool + */ + function getFancySymbol() public view returns (string memory symbol) { if(protocol == Protocol.Uniswap) { address token0 = UniswapInterface(address(poolIncentivized)).token0(); address token1 = UniswapInterface(address(poolIncentivized)).token1(); string memory symbol0 = IERC20Metadata(token0).symbol(); string memory symbol1 = IERC20Metadata(token1).symbol(); - string memory res = string.concat("UNI-V2 (",symbol0); - res = string.concat(res, "-"); - res = string.concat(res, symbol1); - res = string.concat(res, ")"); - return res; + symbol = string.concat("UNI-V2 (",symbol0); + symbol = string.concat(symbol, "-"); + symbol = string.concat(symbol, symbol1); + symbol = string.concat(symbol, ")"); } else { - return getLPSymbol(); + symbol = getLPSymbol(); } } } diff --git a/packages/hardhat/contracts/TournamentFactory.sol b/packages/hardhat/contracts/TournamentFactory.sol index 897884e..3b6cdff 100644 --- a/packages/hardhat/contracts/TournamentFactory.sol +++ b/packages/hardhat/contracts/TournamentFactory.sol @@ -6,6 +6,7 @@ import "./Tournament.sol"; // Use openzeppelin to inherit battle-tested implementations (ERC20, ERC721, etc) // import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; contract TournamentFactory { // State Variables @@ -14,6 +15,12 @@ contract TournamentFactory { mapping(address => address) public TournamentPartner; address public owner; + address public implementationContract; + + constructor (address _owner) { + owner = _owner; + implementationContract = address(new Tournament()); + } //// VRF deployment to Avax. @todo make structs for each chain? Pass in struct to createTournament() for vrf constructor args. // uint64 subscriptionId = 1341; // bytes32 gasLane = 0x354d2f95da55398f44b7cff77da56283d9c6c829a4bdf1bbcaf2ad6a4d081f61; @@ -25,12 +32,6 @@ contract TournamentFactory { address tournament ); - // Constructor: Called once on contract deployment - // Check packages/hardhat/deploy/00_deploy_your_contract.ts - constructor(address _owner) { - owner = _owner; - } - // Modifier: used to define a set of rules that must be met before or after a function is executed // Check the withdraw() function modifier isOwner() { @@ -47,8 +48,6 @@ contract TournamentFactory { * @param _LPTokenAmount (uint256) - amount of the ERC-20 LP token to stake in order to participate * @param _startTime (uint256) - block timestamp at which the tournament starts * @param _endTime (uint256) - block timestamp at which the tournament ends - * - * @return newTournament (Tournament) - instance of the new tournament */ function createTournament( string memory _name, @@ -60,8 +59,9 @@ contract TournamentFactory { bytes32 _gasLane, uint32 _callbackGasLimit, address _vrfCoordinatorV2 - ) public returns (Tournament newTournament) { - newTournament = new Tournament( + ) public { + address instance = Clones.clone(implementationContract); + Tournament(instance).initialize( owner, _name, _poolIncentivized, @@ -73,9 +73,9 @@ contract TournamentFactory { _callbackGasLimit, _vrfCoordinatorV2 ); - TournamentArray.push(address(newTournament)); - TournamentMap[address(newTournament)] = newTournament; - emit TournamentCreated(address(newTournament)); + TournamentArray.push(instance); + TournamentMap[instance] = Tournament(instance); + emit TournamentCreated(instance); } diff --git a/packages/hardhat/contracts/VRFConsumerBaseV2Upgradeable.sol b/packages/hardhat/contracts/VRFConsumerBaseV2Upgradeable.sol new file mode 100644 index 0000000..4bb267a --- /dev/null +++ b/packages/hardhat/contracts/VRFConsumerBaseV2Upgradeable.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +/** **************************************************************************** + * @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(, address _vrfCoordinator, address _link) + * @dev VRFConsumerBase(_vrfCoordinator) public { + * @dev + * @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 VRFConsumerBaseV2Upgradeable is Initializable { + error OnlyCoordinatorCanFulfill(address have, address want); + address private vrfCoordinator; + + function __VRFConsumerBaseV2Upgradeable_init( + address _vrfCoordinator + ) internal onlyInitializing { + 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); + } +} \ No newline at end of file diff --git a/packages/hardhat/deploy/01_deploy_your_contract.ts b/packages/hardhat/deploy/01_deploy_your_contract.ts index 2f080a9..12a9fd0 100644 --- a/packages/hardhat/deploy/01_deploy_your_contract.ts +++ b/packages/hardhat/deploy/01_deploy_your_contract.ts @@ -1,6 +1,6 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import { Contract, randomBytes } from "ethers"; +import { Contract } from "ethers"; /** * Deploys a contract named "YourContract" using the deployer account and @@ -24,27 +24,28 @@ const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvir const UniswapV2Pair = await hre.ethers.getContract("UniswapV2Pair", deployer); const Vyper_contract = await hre.ethers.getContract("Vyper_contract", deployer); // yearn + // await deploy("VRFConsumerBaseV2Upgradeable", { + // from: deployer, + // // Contract constructor arguments + // args: [], + // log: true, + // // autoMine: can be passed to the deploy function to make the deployment process faster on local networks by + // // automatically mining the contract deployment transaction. There is no effect on live networks. + // autoMine: true, + // }); + await deploy("Tournament", { from: deployer, // Contract constructor arguments - args: [ - deployer, - "Tournament", - "0x0000000000000000000000000000000000000000", - 1, - 0, - Math.round(Date.now() / 1000 + 60 * 60 * 24 * 15), - 0, - randomBytes(32), - 0, - "0x0000000000000000000000000000000000000000", - ], + args: [], log: true, // autoMine: can be passed to the deploy function to make the deployment process faster on local networks by // automatically mining the contract deployment transaction. There is no effect on live networks. autoMine: true, }); + // const implementation = await hre.ethers.getContract("Tournament", deployer); + await deploy("TournamentFactory", { from: deployer, // Contract constructor arguments diff --git a/packages/hardhat/test/foundry/TournamentTest.t.sol b/packages/hardhat/test/foundry/TournamentTest.t.sol index 1451e62..5ca7bf1 100644 --- a/packages/hardhat/test/foundry/TournamentTest.t.sol +++ b/packages/hardhat/test/foundry/TournamentTest.t.sol @@ -134,7 +134,8 @@ contract TournamentTest is Test { string memory name = "Yearn Tournament"; // Deploy Tournament contract - tournamentY = new Tournament( + tournamentY = new Tournament(); + tournamentY.initialize( owner, name, address(mockYLP), LPTokenAmount, startTime, endTime, subId, gasLane, callbackGasLimit, address(mockVRF) @@ -142,7 +143,8 @@ contract TournamentTest is Test { name = "Uniswap Tournament"; // Deploy Tournament contract - tournamentU = new Tournament( + tournamentU = new Tournament(); + tournamentU.initialize( owner, name, address(mockUniLP), LPTokenAmount, startTime, endTime, subId, gasLane, callbackGasLimit, address(mockVRF) @@ -500,16 +502,16 @@ contract TournamentTest is Test { assertEq(tournamentU.getPricePerShare(), uint(_fuzz0) * uint(_fuzz1) / 1000 ether); } - function test_LPTokenAmountOfPlayer_notValuated() public { + function test_withdrawAmountFromDeposit_notValuated() public { vm.warp(duringTime); stakeForTest(player1); - assertEq(tournamentY.LPTokenAmountOfPlayer(player1), LPTokenAmount); - assertEq(tournamentU.LPTokenAmountOfPlayer(player1), LPTokenAmount); + assertEq(tournamentY.withdrawAmountFromDeposit(player1), LPTokenAmount); + assertEq(tournamentU.withdrawAmountFromDeposit(player1), LPTokenAmount); } - function test_LPTokenAmountOfPlayer_valuated() public { + function test_withdrawAmountFromDeposit_valuated() public { vm.warp(duringTime); stakeForTest(player1); @@ -518,16 +520,16 @@ contract TournamentTest is Test { mockUniLP.setReserves(1500 ether, 2000 ether); mockUniLP.setTotalSupply(2000 ether); - assertEq(tournamentY.LPTokenAmountOfPlayer(player1), LPTokenAmount * 10 / 15); - assertEq(tournamentU.LPTokenAmountOfPlayer(player1), LPTokenAmount * 10 / 15); + assertEq(tournamentY.withdrawAmountFromDeposit(player1), LPTokenAmount * 10 / 15); + assertEq(tournamentU.withdrawAmountFromDeposit(player1), LPTokenAmount * 10 / 15); mockUniLP.setReserves(2000 ether, 1000 ether); mockUniLP.setTotalSupply(1000 ether); - assertEq(tournamentU.LPTokenAmountOfPlayer(player1), LPTokenAmount / 2); + assertEq(tournamentU.withdrawAmountFromDeposit(player1), LPTokenAmount / 2); } - function test_LPTokenAmountOfPlayer_devaluated() public { + function test_withdrawAmountFromDeposit_devaluated() public { vm.warp(duringTime); stakeForTest(player1); @@ -535,12 +537,12 @@ contract TournamentTest is Test { mockYLP.setPricePerShare(50000); mockUniLP.setReserves(500 ether, 500 ether); - assertEq(tournamentY.LPTokenAmountOfPlayer(player1), LPTokenAmount); - assertEq(tournamentU.LPTokenAmountOfPlayer(player1), LPTokenAmount); + assertEq(tournamentY.withdrawAmountFromDeposit(player1), LPTokenAmount); + assertEq(tournamentU.withdrawAmountFromDeposit(player1), LPTokenAmount); mockUniLP.setTotalSupply(2000 ether); - assertEq(tournamentU.LPTokenAmountOfPlayer(player1), LPTokenAmount); + assertEq(tournamentU.withdrawAmountFromDeposit(player1), LPTokenAmount); } function test_unstakeLPToken_before() public { diff --git a/packages/nextjs/components/tournament/withdraw.tsx b/packages/nextjs/components/tournament/withdraw.tsx index f6b8368..bf0e1e3 100644 --- a/packages/nextjs/components/tournament/withdraw.tsx +++ b/packages/nextjs/components/tournament/withdraw.tsx @@ -26,10 +26,10 @@ export const Withdraw = () => { functionName: "getLPDecimals", }); - const { data: LPTokenAmountOfPlayer } = useContractRead({ + const { data: withdrawAmountFromDeposit } = useContractRead({ abi: DeployedContracts[31337].Tournament.abi, address: params.addr, - functionName: "LPTokenAmountOfPlayer", + functionName: "withdrawAmountFromDeposit", args: [connectedAddress], }); @@ -65,7 +65,7 @@ export const Withdraw = () => { }, }); - if (Number(LPTokenAmountOfPlayer) == 0) { + if (Number(withdrawAmountFromDeposit) == 0) { return (
@@ -102,14 +102,14 @@ export const Withdraw = () => { and get your rewards

- {Number(formatUnits(LPTokenAmountOfPlayer || 0n, Number(LPTokenDecimals.data) || 18)).toFixed(2)}{" "} + {Number(formatUnits(withdrawAmountFromDeposit || 0n, Number(LPTokenDecimals.data) || 18)).toFixed(2)}{" "} {LPTokenSymbol.isLoading ? "..." : LPTokenSymbol.data} can be withdrawn from your deposit and you earned{" "} {Number(formatUnits(prizeAmount || 0n, Number(LPTokenDecimals.data) || 18)).toFixed(2)}{" "} {LPTokenSymbol.isLoading ? "..." : LPTokenSymbol.data}.