As of 30 Oct 2026, XYRO has 4 audit reports by 2 security firms on Trustblock; the latest is by CertiK, dated 10 Jan 2025. Those reports list 1 critical acknowledged and not fixed.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| XYRO - audits | Security firm: CertiK | Date: | Scope: Repository at commit 0a08cfa | No open critical findings | Open · skynet.certik.com |
| Xyro | Security firm: Zokyo | Date: | Scope: Repository at commit 8663619 | No open critical findings | Open · IPFS |
| Xyro | Security firm: Zokyo | Date: | Scope: Repository at commit cd1ae21 | No open critical findings | Open · IPFS |
| Xyro | Security firm: Zokyo | Date: | Scope: Repository at commit b7f77de | 1 critical acknowledged, not fixed | Open · IPFS |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 1 | 5 |
| High | 3 | 0 | 7 |
| Medium | 1 | 2 | 19 |
| Low | 5 | 4 | 39 |
The report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportIn the `closeGame()` function, as long as the game state is "Created" or the current time is three days after the game's `endTime`, an attacker can call `closeGame()` to refund the initiator's deposit repeatedly: `OneVsOneExactPrice.sol` ```solidity=405 function closeGame(bytes32 gameId) public { GameInfo memory game = decodeData(gameId); require(game.initiator == msg.sender, "Wrong sender"); require( game.gameStatus == Status.Created || ( block.timestamp > game.endTime ? block.timestamp - game.endTime >= 3 days : false ), "Wrong status!" ); ITreasury(treasury).refund( games[gameId].depositAmount, game.initiator, gameId ); //rewrites status games[gameId].packedData2 = (games[gameId].packedData2 & ~(uint256(0xFF) << 208)) | (uint256(uint8(Status.Cancelled)) << 208); emit ExactPriceCancelled(gameId); } ``` As a result, when the current time is three days after the `endTime`, there is no need to check the game state, and the refund can be made to the initiator. This allows `closeGame()` to be called repeatedly.For games in the "started" state, repeated calls by the initiator will also "refund" the opponent's deposit amount to initiator. In addition, when the game's lock record in treasury is large enough, the lock amount can be continuously withdrawn through this function. By combining this with the vulnerability in `createGameWithPermit`, which allows the repeated creation of a game with the same `gameId`, low-value tokens can be exchanged for high-value tokens.
Open the reportIn the `UpDown` contract, the `finalizeGame` function is responsible for finalizing the game and distributing rewards. However, when the game is canceled due to either `UpPlayers.length == 0` or `DownPlayers.length == 0`, the function resets several global storage variables but fails to reset `totalRakebackUp` and `totalRakebackDown`. This oversight can lead to incorrect calculations and potential asset loss in subsequent game rounds. The critical section of the code is: ```solidity if (UpPlayers.length == 0 || DownPlayers.length == 0) { // ... refund logic ... packedData = 0; totalDepositsUp = 0; totalDepositsDown = 0; currentGameId = bytes32(0); return; } // ... other logic ... delete DownPlayers; emit UpDownCancelled(currentGameId); packedData = 0; currentGameId = bytes32(0); return; // ... other logic ... delete DownPlayers; delete UpPlayers; ITreasury(treasury).setGameFinished(currentGameId); currentGameId = bytes32(0); packedData = 0; totalDepositsUp = 0; totalDepositsDown = 0; ``` The same issue is present at the end of the `finalizeGame` function where `totalRakebackUp` and `totalRakebackDown` are not reset, potentially affecting future games.
Open the reportIn this function, there is no check like the one in `createGame` and `createGameWithDeposit` where `require(games[gameId].packedData == 0, "Game exists")` is used to verify if the current `gameId` already exists. As a result, when two `createGameWithPermit` transactions with the same `endTime` and both being public games (opponent is `address(0)`) are processed in the same block, the first created game will be overwritten by the second. Consequently, the user's bet for the first game will be locked in the contract and cannot be refunded or withdrawn through the game. In addition, since the locked amount for the game corresponding to the `gameId` has increased before the overwrite, this can be exploited in combination with the vulnerability that allows `closeGame()` to be called multiple times. This can lead to the exchange of low-value tokens for high-value tokens.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportIn the case where `UpPlayers.length == 0 || DownPlayers.length == 0`, the game is invalid, and the game principal should be refunded to the players, followed by a return. However, before the return, `startingPrice` is not set to zero: `UpDown.sol` ```solidity=304 emit UpDownCancelled(currentGameId); packedData = 0; totalDepositsUp = 0; totalDepositsDown = 0; totalRakebackUp = 0; totalRakebackDown = 0; currentGameId = bytes32(0); return; ``` This will result in an existing `startingPrice` before the next game starts.
Open the reportWhen `uint192(finalPrice / 1e14) == _game.startingPrice`, after refunding the tokens to the user, the user's `depositAmounts` are not set to 0, and `totalDepositsUp` and `totalDepositsDown` are not reset to 0: `UpDown.sol` ```solidity=348 else if (uint192(finalPrice / 1e14) == _game.startingPrice) { for (uint i; i < UpPlayers.length; i++) { ITreasury(treasury).refund( depositAmounts[UpPlayers[i]], UpPlayers[i], currentGameId ); isParticipating[UpPlayers[i]] = false; } delete UpPlayers; for (uint i; i < DownPlayers.length; i++) { ITreasury(treasury).refund( depositAmounts[DownPlayers[i]], DownPlayers[i], currentGameId ); isParticipating[DownPlayers[i]] = false; } delete DownPlayers; emit UpDownCancelled(currentGameId); packedData = 0; currentGameId = bytes32(0); return; } ``` This will cause the variables from the current round to be included in the next round's game, leading to confusion. It may result in the `finalizeGame()` function for the next game being unable to execute.
Open the reportIn the `Treasury` contract, the `distributeBullseye` function is responsible for deducting the appropriate rakeback for each guess during reward distribution. However, if a player participates multiple times in a single round, the rakeback calculation may be incorrect. For instance, if userA participates twice with different guesses and userB participates once, and userA's second guess wins, both userA and userB are meant to retain one share of rakeback, with userA winning the reward. The issue arises when calculating `winnersRakeback`, as all of userA's rakeback entries are considered, including those from losing guesses. This results in insufficient rakeback for players to withdraw. ```solidity for (uint i = 0; i < 3; i++) { if (currentRates[i] != 0) { winnersRakeback += ITreasury(treasury).lockedRakeback( currentGameId, topPlayers[i] ); } } for (uint256 i = 0; i < 3; i++) { if (topPlayers[i] != address(0)) { if (currentRates[i] != 0) { ITreasury(treasury).distributeBullseye( currentRates[i], totalRakeback - winnersRakeback, topPlayers[i], currentGameId, topRakeback[i] ); } } } ```
Open the reportThe `playWithPermit` function of `Bullseye` contract incorrectly calculates and double-counts the rakeback. The line `uint256 rakeback = totalRakeback += ITreasury(treasury).depositAndLockWithPermit(...)` both assigns and adds the value of rakeback to `totalRakeback`, leading to an incorrect rakeback value being stored in `playerGuessData` and emitted in the `BullseyeNewPlayer` event. ```solidity function playWithPermit( uint256 assetPrice, ITreasury.PermitData calldata permitData ) public { ... @> uint256 rakeback = totalRakeback += ITreasury(treasury) .depositAndLockWithPermit( depositAmount, msg.sender, currentGameId, playerGuessData.length, permitData.deadline, permitData.v, permitData.r, permitData.s ); ... @> totalRakeback += rakeback; emit BullseyeNewPlayer( msg.sender, assetPrice, depositAmount, currentGameId, playerGuessData.length - 1, rakeback ); } ```
Open the reportThe `lock` function is responsible for locking tokens for a game and is invoked by other functions like `playWithDeposit` and `createGameWithDeposit`. However, unlike the deposit functions `depositAndLock`, `depositAndLockWithPermit` etc , `lock` does not enforce the requirement that the amount must be greater than or equal to `minDepositAmount[token]`. This omission allows players to lock fewer tokens than intended, potentially undermining game mechanics. Below is the relevant code snippet: ```solidity function lock( uint256 amount, address from, bytes32 gameId, bool isRakeback ) public onlyRole(DISTRIBUTOR_ROLE) returns (uint256 rakeback) { address token = gameToken[gameId]; require(approvedTokens[token], "Unapproved token"); require(deposits[token][from] >= amount, "Insufficent deposit amount"); // Missing minDepositAmount[token] check if (isRakeback) { rakeback = calculateRakebackAmount(from, amount); lockedRakeback[gameId][from] += rakeback; } deposits[token][from] -= amount; locked[gameId] += amount; } ```
Open the reportThe `fee` variables in the contract have no set limits, allowing fees to potentially reach 100%. If this occurs, users would receive no tokens from the contract, resulting in a complete loss of their investment.
Open the reportIn the `OneVsOneExactPrice` contract, the `closeGame` function allows the game's initiator to close the game and refund tokens when the game is in the `Created` status or when the current time is three days after `game.endTime`. However, if the game is `Started`, the function only refunds the initiator's assets and does not account for the opponent's assets. Furthermore, there are no other functions available to refund the opponent's assets, potentially causing their assets to be locked indefinitely. The relevant code snippet is: ```solidity function closeGame(bytes32 gameId) public { GameInfo memory game = decodeData(gameId); require(game.initiator == msg.sender, "Wrong sender"); require( game.gameStatus == Status.Created || ( block.timestamp > game.endTime ? block.timestamp - game.endTime >= 3 days : false ), "Wrong status!" ); ITreasury(treasury).refund( games[gameId].depositAmount, game.initiator, gameId ); //rewrites status games[gameId].packedData2 = (games[gameId].packedData2 & ~(uint256(0xFF) << 208)) | (uint256(uint8(Status.Cancelled)) << 208); emit ExactPriceCancelled(gameId); } ```
Open the reportIn the `finalizeGame()` function, the initiator fee is rounded only once during the calculation for the winning side: ```solidity=555 finalRate = ITreasury(treasury).calculateRate( games[gameId].totalDepositsTP - ((games[gameId].totalDepositsTP * initiatorFee) / FEE_DENOMINATOR), games[gameId].totalRakebackSL, gameId ); ``` However, in the `retrieveRewards()` function, each winner may have their initiator fee rounded when withdrawing, which can lead to multiple rounding in the total calculation: ```solidity=635 ITreasury(treasury).universalDistribute( msg.sender, depositAmounts[gameIds[i]][msg.sender] - ((depositAmounts[gameIds[i]][msg.sender] * initiatorFee) / FEE_DENOMINATOR), gameIds[i], games[gameIds[i]].finalRate ); ``` In this case, the total rewards withdrawn by each winner may exceed the amount stored in `locked[gameId]`, causing some users' funds (either rakeBack or prize money, depending on who withdraws the reward last) to be unable to be extracted from the treasury.
Open the reportIn the `Treasury` contract, the `withdrawInitiatorFee` function is designed to calculate and distribute the initiator's fee from the game deposits. However, the formula used to calculate `withdrawnFees` is incorrect. The current calculation is: ```solidity withdrawnFees = (wonTeamDeposits + lostTeamDeposits * initiatorFee) / FEE_DENOMINATOR; ``` This formula erroneously applies the `initiatorFee` only to `lostTeamDeposits`,and the winning team will be charged a 0.01% fee, leading to an incorrect fee calculation. According to [xyro.io](https://xyro.io/onboarding/one-vs-one),the fee should be calculated based on the combined total of `wonTeamDeposits` and `lostTeamDeposits`, multiplied by the rate `initiatorFee/FEE_DENOMINATOR`. This error can result in an incorrect initiator fee, causing potential asset loss to the game initiator.
Open the reportDue to forgetting to set `isParticipating` of `msg.sender` to `true`, `msg.sender` can play the same game round multiple times, with their `deposits` being set to the amount of their last bet: ```solidity=126 function playWithDeposit(bool isLong, uint256 depositAmount) public { require(depositAmount >= minDepositAmount, "Wrong deposit amount"); require(!isParticipating[msg.sender], "Already participating"); require( DownPlayers.length + UpPlayers.length + 1 <= maxPlayers, "Max player amount reached" ); GameInfo memory game = decodeData(); require( game.stopPredictAt > block.timestamp && (game.totalDepositsDown + depositAmount <= type(uint32).max || game.totalDepositsUp + depositAmount <= type(uint32).max), "Game is closed for new players" ); if (isLong) { //rewrites totalDepositsUp packedData = (packedData & ~(uint256(0xFFFFFFFF) << 168)) | ((depositAmount + game.totalDepositsUp) << 168); UpPlayers.push(msg.sender); } else { //rewrites totalDepositsDown packedData = (packedData & ~(uint256(0xFFFFFFFF) << 136)) | ((depositAmount + game.totalDepositsDown) << 136); DownPlayers.push(msg.sender); } depositAmounts[msg.sender] = depositAmount; ITreasury(treasury).lock(depositAmount, msg.sender); emit UpDownNewPlayer(msg.sender, isLong, depositAmount, currentGameId); } ``` An attacker can repeatedly place small bets (e.g., 1 USDT) and then place a larger bet (e.g., 2 USDT). As a result, all of their bets will be updated to the last bet's amount (2 USDT) during the game finalization.At this point, the treasury will revert due to an underflow when it tries to distribute funds, because the funds locked in the contract for that round of the game are insufficient. This results in the `finalizeGame()` transaction failing.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportFor refunds, rakeback should not be calculated and added to the user's deposits. This portion of the rakeback is redundant and could potentially cause an underflow in `locked[gameId]`. Since the current game, `OneVsOneExactPrice`, does not calculate rakeback, underflow will not occur for now. However, future games may encounter this issue.
Open the reportSince the state at the first position in the `enum` corresponds to the number 0, any unstarted game is in state 0, i.e., the "Created" state. This makes the game state checks in some functions of the contract ineffective.
Open the reportIn the `Setup` contract, the `GAME_MASTER_ROLE` has the ability to finalize the game before the designated end time. Finalization is permissible when the game's status is set to `Created`, and the `finalPrice` falls within the range of `stopLossPrice` and `takeProfitPrice`. This behavior is inconsistent with other games such as `Bullseye`, `OneVsOneExactPrice`, and `UpDown`, where finalization is only allowed after the `game.endTime` has been reached.
Open the reportIn the `Bullseye` contract, the `exactRange` variable is used to determine if a player's guessed price is sufficiently close to the `finalPrice`, which in turn affects the distribution rate. Previously, the price was represented with 4 decimals, and the default `exactRange` was set to `50000`. However, in the current version, the price is represented with 18 decimals. Continuing to use `50000` as the default `exactRange` value may lead to incorrect evaluations due to the increased precision. This could potentially affect the fairness and accuracy of the game's outcome.
Open the reportThe `retrieveRewards` function in the smart contract allows users to claim rewards based on game outcomes. However, when withdrawing rakeback assets, the function does not update the `withdrawStatus` for the user, which could lead to inconsistency in tracking withdrawal statuses. Below is the relevant code snippet: ```solidity if (withdrawStatus[gameIds[i]][msg.sender] == UserStatus.SL) { require( ITreasury(treasury).lockedRakeback( gameIds[i], msg.sender ) != 0, "You lost" ); ITreasury(treasury).withdrawRakebackSetup( gameIds[i], msg.sender ); // Missing update to withdrawStatus here } ``` Due to the weak validation of game status in `closeGame()`, if the official mistakenly calls `closeGame()` after `finalizeGame()`, it will allow the losing players to refund their principal in the game after receiving the rakeBack.
Open the reportContract `Treasury` extends `AccessControlUpgradeable`, but the extended contract is not initialized by the current contract. Generally, the initializer function of a contract should always call all the initializer functions of the contracts that it extends.
Open the report`createSetup()` lacks a check for the existence of `gameId`. When a user creates two games with the same `gameId` at the same time, the second one will overwrite the first.
Open the report`UpDown.sol` ```solidity=234 function setStartingPrice( bytes memory unverifiedReport ) public onlyRole(GAME_MASTER_ROLE) { GameInfo memory game = decodeData(); require(block.timestamp >= game.stopPredictAt, "Too early"); require( UpPlayers.length != 0 || DownPlayers.length != 0, "Not enough players" ); address upkeep = ITreasury(treasury).upkeep(); (int192 startingPrice, uint32 priceTimestamp) = IDataStreamsVerifier( upkeep ).verifyReportWithTimestamp(unverifiedReport, game.feedNumber); require( block.timestamp - priceTimestamp <= 1 minutes, "Old chainlink report" ); packedData |= uint192(startingPrice / 1e14) << 104; emit UpDownStarted(startingPrice, currentGameId); } ``` Since `setStartingPrice` only requires `block.timestamp` to be greater than or equal to `game.stopPredictAt` and does not require `block.timestamp` to be less than `game.endTime`, this allows the game master to set the starting price after the game has ended. The function does not check if the `startingPrice` in `packedData` is 0, which allows the game master to repeatedly call the function to set the `startingPrice` multiple times after `game.stopPredictAt`. The function only checks `require(block.timestamp - priceTimestamp <= 1 minutes, "Old chainlink report");`, but does not restrict the time difference between `priceTimestamp` and `game.stopPredictAt`, which allows an expired price to be set as the starting price.
Open the reportIn the `Setup` contract, the process of finalizing a game involves decoding the `finalPrice` and `endTime` from the `unverifiedReport`. However, unlike other game contracts, the `Setup` contract does not validate whether the `endTime` is outdated. This oversight can lead to the contract utilizing an outdated report, potentially causing incorrect game finalization and financial discrepancies. Code snippet: ```solidity (int192 finalPrice, uint256 endTime) = IDataStreamsVerifier( ITreasury(treasury).upkeep() ).verifyReportWithTimestamp(unverifiedReport, data.feedNumber); ```
Open the reportIn the `Bullseye` and `UpDown` contracts, `minDepositAmount` and `depositAmount` is set when starting a game. However, there is no validation to ensure that `minDepositAmount` and `depositAmount` matches the `minDepositAmount[token]` specified in the `Treasury` contract. This lack of validation can prevent players from joining the game if the deposit amount is incorrect.
Open the reportThe cited address input is missing a check that it is not `address(0)`.
Open the reportSeveral contracts implement a feature allowing users to grant token allowances through a permit signature. However, there is a compatibility issue with the `DAI` stablecoin's unique permit function signature. The `DAI` contract at address `0x6B175474E89094C44Da98b954EedeAC495271d0F` defines its permit function with the following parameters: `function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s)`. This signature differs from the expected format, resulting in failed transactions because the contract's parameters do not align with those required by DAI's permit function. Consequently, any attempt to execute permit transactions with the incorrect parameters will inevitably fail.
Open the reportIn the provided code snippets, game IDs are generated using the `keccak256` hash function with parameters such as `block.timestamp`, `endTime`, `msg.sender`, and other variables. The concern arises when multiple instances of the same game contract are deployed, and they share a common treasury contract. This can lead to potential game ID collisions. OneVsOneExactPrice Contract: ```solidity bytes32 gameId = keccak256( abi.encodePacked(endTime, block.timestamp, msg.sender, opponent) ); ``` Setup Contract: ```solidity bytes32 gameId = keccak256( abi.encodePacked( block.timestamp, endTime, takeProfitPrice, stopLossPrice, msg.sender ) ); ``` If the same parameters are used across different game contracts, the generated game IDs might be identical. When these IDs are used in a shared treasury contract, they could interfere with each other, potentially leading to asset loss and other unintended side effects.
Open the reportThe `startGame` function allows the creation of a game with specified parameters including `endTime` and `stopPredictAt`. Currently, it only checks if `endTime` is greater than `stopPredictAt`: `UpDown.sol` ```solidity=74 require(endTime > stopPredictAt, "Ending time must be higher"); ``` or `endTime` is greater than `block.timestamp`: `Bullseye.sol` ```solidity=91 require(endTime > block.timestamp, "Wrong ending time"); ``` This condition is insufficient as it does not enforce a significant time gap between `stopPredictAt` and `endTime`. If the gap is too small, players who join later have an advantage due to the predictability of price changes over short periods. This could lead to an unfair game environment.
Open the reportIn all games, the price precision used is 4 decimals. This is suitable for higher-priced tokens. However, for lower-priced tokens, this design doesn't effectively capture the price changes of the token. For example, for a token priced at \$1000, a price change of 0.00001%(\$0.0001) would be considered a price change in the game. However, for a token priced at \$0.01, a price change of 1%(\$0.0001) would need to occur before it is recognized as a price change. At the same time, using `uint32` to store 4-decimal prices allows for a maximum token price of around \$420,000. This will not be scalable in the future when token prices increase, and it also prevents the inclusion of some high-priced tokens, such as high-value LP tokens from Uniswap V2.
Open the reportAccording to the official documentation [xyro.io](https://xyro.io/onboarding/bulls-eye), when the number of participants is between 2 and 5, if the price is hit exactly, the first place receives 100%, otherwise, the first place gets 90% and the second place receives 10%. However, the code does not differentiate this situation and simply awards 100% of the rate bonus to the first place: `Bullseye.sol` ```solidity=14 uint256[3][5] public rates = [ [10000, 0, 0], [7500, 2500, 0], [9000, 1000, 0], [5000, 3500, 1500], [7500, 1500, 1000] ]; ``` `Bullseye.sol` ```solidity=306 if (packedGuessData.length <= 5) { ITreasury(treasury).withdrawGameFee( totalDeposited - depositAmount, fee, currentGameId ); currentRates = rates[0]; } else if (packedGuessData.length <= 10) { ITreasury(treasury).withdrawGameFee( totalDeposited - 2 * depositAmount, fee, currentGameId ); currentRates = closestDiff[0] <= exactRange ? rates[2] : rates[1]; } else { ITreasury(treasury).withdrawGameFee( totalDeposited - 3 * depositAmount, fee, currentGameId ); currentRates = closestDiff[0] <= exactRange ? rates[4] : rates[3]; } ``` There is an error in the official documentation regarding the distribution for exact price hits when there are 10+ players. It should align with the contract: 1st place - 75%, 2nd place - 15%, 3rd place - 10%. Additionally, the current `getRateIndex` function returns an index with a maximum value of 5, but the largest index in the `rates` array is 4: `Bullseye.sol` ```solidity=453 function getRateIndex( uint256 playersCount, bool isExact ) public pure returns (uint256 index) { if (playersCount <= 5) { index = isExact ? 1 : 0; } else if (playersCount <= 10) { index = isExact ? 3 : 2; } else { index = isExact ? 5 : 4; } } function setRate( uint256[3] memory rate, uint256 playersCount, bool isExact ) public onlyRole(DEFAULT_ADMIN_ROLE) { rates[getRateIndex(playersCount, isExact)] = rate; } } ```
Open the reportThe compiler for Solidity 0.8.20 switches the default target EVM version to [Shanghai](https://blog.soliditylang.org/2023/05/10/solidity-0.8.20-release-announcement/#important-note), which includes the new `PUSH0` op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier [EVM](https://docs.soliditylang.org/en/v0.8.20/using-the-compiler.html?ref=zaryabs.com#setting-the-evm-version-to-target) [version](https://book.getfoundry.sh/reference/config/solidity-compiler#evm_version)
Open the reportSince Solidity Release 0.8.23, `MCOPY` opcode is introduced. However, this may not be compatible with all chains and L2s. As a result, the compatibility of the code could be affected.
Open the reportIn `startGame()`, there is no validation to check if `stopPredictAt` is greater than the current `block.timestamp`, and `feedNumber` is not validated to ensure it is a valid `feedNumber` capable of producing a price.
Open the reportThe `Treasury` logic contract does not protect the initializer. An attacker can front-run the `initialize` call and assume ownership of the logic contract. Once in control, the attacker can perform privileged operations, misleading users into believing that they are interacting with the legitimate owner of the upgradeable contract.
Open the reportIn `setUp`, as long as `block.timestamp > data.endTime`, the game master can call the `closeGame()` function: `Setup.sol` ```solidity=355 function closeGame(bytes32 gameId) public onlyRole(GAME_MASTER_ROLE) { GameInfo memory data = decodeData(gameId); require(data.startTime != 0, "Game doesn't exist"); require( ((data.startTime + (data.endTime - data.startTime) / 3 < block.timestamp && (data.SLplayers == 0 || data.TPplayers == 0)) || block.timestamp > data.endTime), "Wrong status!" ); //rewrites status games[gameId].packedData2 = (games[gameId].packedData2 & ~(uint256(0xFF) << 72)) | (uint256(uint8(Status.Cancelled)) << 72); emit SetupCancelled(gameId, data.initiator); } ``` When the game master mistakenly calls this function, changing the game’s status from "finished" to "cancelled", it will allow the losing player to refund their principal from the game, while preventing the winning player from receiving the reward. Only the principal can be refunded to winning player.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="xyro"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us