CertiK reviewed Gooeys.io (Gooeys) on 15 Jul 2022: 49 findings, 0 critical; 23 marked fixed, 26 open.
The original report was removed from its address (seen 20 Oct 2026).
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 5 | 0 | 1 |
| Medium | 2 | 0 | 2 |
| Low | 19 | 0 | 20 |
Functions with the `isGameController()` modifier can either be used by any external address which is given `gameController` status, but also allows the owner to call. The function `mint()` for the `Goo` contract is one such function. Should any of the `gameController` or `owner` addresses be compromised, a hacker can gain control and mint any desired amount of tokens to any chosen address. In the contract `Goo` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and set new gameControllers, enable or disable walletSupplyLimitsEnabled or update the treasury address. 
In the contract `GooeyFruit` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and for example set a new value for the private variable `_uri` (inherited from `ERC1155.sol`) or change the `ALT_TOKEN` through the `loadAltToken()` function. 
In the contract `Gooeys` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and, for example, load malicious address for `D_TOKEN`, `G_TOKEN` and `G_FOOD`, set new values for fees, pause the game or change the `baseURI` address. 
In the file Goo.sol, all of the `GTO` tokens are sent to the `msg.sender` when deploying the contract.
The owner can update any address to be exempt from the contract check performed by the modifier `contractGweiLimited()`. While unprivileged external accounts cannot use contracts to interact with the `Gooey` contract functions like `tumble()`, `consumeFruit()`, `startQuest()` and so on, the owner can use a contract to do so.
It is assumed that `G_TOKEN` is a reference to the contract `Goo` in Goo.sol. When the function `safeTransferFrom()` is called using the `msg.sender` as the sender and `DEAD` as the recipient, this function call bypasses the traditional `burn()` function mechanism in the `ERC20` base contract. As such, rather than sending the tokens to `address(0)`, the address `DEAD` is used, forming an inconsistency in disposal. More importantly, using `transferFrom()` rather than `burn()` means the `totalSupply()` of the `Goo` token is not updated. This can create logical inconsistencies for the token.
From context, it appears that the only callers of this function should be either the owner or the other contracts, `Goo` and `Gooeys`. However, if that's not the case, and someone else is labeled a "game controller," then this function is vulnerable to attack. The `random` number generation is based on the result of hashing using `keccak256`. This randomness result can be computed by another `Gamecontroller`. Therefore this function call has the potential to be frontrun.
If `gameStarted` bool is changed from false to true more than one time, then `gameStartBlock` will not be updated to the current `block.number` all consequent times, since `gameStartBlock` value will no longer be 0. For example, in the scenario above, in the `checkIn()` function, the value for `lastUpdateBlock` would be checked against the block number in which the game was originally started, instead of the most recent time in which `gameStarted` status is changed.
The condition that that `tx.origin == msg.sender` is used to determine if the caller is a contract address or EOA. This places multiple restrictions on the modifier `contractGweiLimited` such as: multisignature wallets cannot call functions with this modifier and a loss of composability.
Inside `Goo.sol` the modifier `walletSupplyLimitCheck` is used to prevent an address from containing more than allowed supply. This modifier limits the balance a single address can hold. However, any user can create an infinite number of wallets.
Anyone can call the function `checkIn()` for any `tokenId`, whether or not they own the token. This may leave a user with a lack of control over when given attributes are updated for their own tokens.
In the contract `Gooeys.sol`, there are numerous functions and declarations that are commented out.
Boolean constants can be used directly and do not need to be compared to true or false. Inside `Goo.sol` at line 37 this comparison is made ```soldiity gameControllers[msg.sender] == false ``` --- Inside `Goo.sol` at line 69 this comparison is made ```soldiity supplyLimitExemptions[_addr] == true ``` --- Inside `GooeyFruit.sol` at line 41 this comparison is made ```soldiity gameControllers[msg.sender] == false ``` --- Inside `Gooeys.sol` at line 187 this comparison is made ```soldiity gameControllers[msg.sender] == false ``` --- Inside `Gooeys.sol` at line 683 this comparison is made ```soldiity quests[gooey1].inProgress == false ``` --- Inside `Gooeys.sol` at line 684 this comparison is made ```soldiity quests[gooey2].inProgress == false ```
In `Goo.sol`, initial supply of GOO token is 2 billion with a precision of 9 decimals. This contract inherits from an edited version of ERC20 OpenZeppelin base contract where decimals is edited to 9. Note that major currencies have an accuracy of 18 decimal places. Therefore external usage of this token may cause calculation problems when exchanging GOO tokens with other currencies, or interacting with external contracts outside the scope of this audit.
In `Gooeys.sol` there are many functions containing hardcoded values. A few examples are `getNewGenTumbles()` and `pickGeneticStat()`. Since there are no comments on what these values are used for, it is unknown whether they are the correct values.
In the context of the contracts Gooey.sol, GooeyFruit.sol, and Goo.sol, each contract is to have the status of "game Controller" in order to make external calls between the contracts when necessary. However, this is not explicitly stated within the file. Moreover, other addresses may be given the status of "game Controller" and in that scenario, may not work as intended with this contract.
While it is assumed that `G_TOKEN` refers to the `Goo` contract, and `G_CORE` refers to the `Gooeys` contract, this is not explicitly stated within the documentation of the code. In each of Gooeys.sol and GooeyFruit.sol, there are state variables in which it is not explicitly clear what contracts are being used as input. Particularly: - For `Gooeys.sol`: `G_TOKEN`,`D_TOKEN`, `G_FOOD` and `GOOEYS_OLD` - For `GooeyFruit.sol`: `G_TOKEN`, `ALT_TOKEN` and `G_CORE` These variables may refer to unaudited contracts, and may not be compatible with the contract code.
The values set in `rarityThresholds` mapping do not conform to what is declared in the docs here https://dogira.gitbook.io/goobook/game-mechanics/cosmetics#selection-rolls-item-pools For example for rare traits the declared values are: - Background / Foreground = 1 - Ears = 29 - Body = 18 - Mask = 23 - Eyes = 25 - Accessory = 5 but the values inside the code are: - Background / Foreground = 8 - Ears = 46 - Body = 40 - Mask = 44 - Eyes = 39 - Accessory = 10
In `Goo.sol` at line 27 is not checked if `_treasury` address passed as the argument of the constructor is the zero address. --- In `Goo.sol` at line 43 within `walletSupplyLimitCheck()` modifier are not checked if `from` and `to` addresses passed as the argument of the constructor are the zero address. Furthermore is not checked also if the `amount` is greater than zero. --- In `Goo.sol` at line 52 within `setGameController()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `Goo.sol` at line 58 within `setSupplyLimitExemption()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `Goo.sol` at line 63 within `setWalletSupplyLimits()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `Goo.sol` at line 82 within `updateTreasury()` function is not checked if `_treasury` address passed as the argument of the function is the zero address. --- In `Goo.sol` at line 87 within `mint()` function is not checked if `to` address passed as the argument of the function is the zero address. Furthermore is not checked also if the `amount` is greater than zero. --- In `GooeyFruit.sol` at line 47 within `setGameController()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `GooeyFruit.sol` at line 51 within `setGameCore()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `GooeyFruit.sol` at line 93 within `mintRoll()` function is not checked if `_addr` address passed as the argument of the function is the zero address. Furthermore is not checked also if the `_rollAmt` is greater than zero. --- In `GooeyFruit.sol` at line 116 within `loadGToken()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `GooeyFruit.sol` at line 128 within `loadAltToken()` function is not checked if `_addr` address passed as the argument of the function is the zero address. --- In `GooeyFruit.sol` at line 180 within `buyFood()` function is not checked if `_receipient` address passed as the argument of the function is the zero address. --- In `GooeyFruit.sol` at line 189 within `buyFoodAltToken()` function is not checked if `_receipient` address passed as the argument of the function is the zero address. --- In `Gooeys.sol` at line 193 within `loadExterns()` function are not checked if `_dToken`, `_tokenAddr` and `_foodAddr` addresses passed as the argument of the function are the zero address. --- In `Gooeys.sol` at line 240 is not checked if `_gooeysOld` address passed as the argument of the constructor is the zero address. --- In `Gooeys.sol` at line 455 within `tumbleNewGooey()` function is not checked if `tokenId` passed as the argument of the function already exists. --- In `Gooeys.sol` at line 884 within `setGameController()` function is not checked if `_addr` address passed as the argument of the function is the zero address.
There should always be events emitted in the sensitive functions that are controlled by centralization roles.
When using a **require** or **revert** statement it is good practice to provide a string message containing details about the error that will be passed back to the caller. --- Related to this note, consider the function `convertOldGooeyCollection()`. This function uses a for-loop to cycle through several token ID values to convert from old NFTs to new. It is possible that during the loop, a token ID already exists between the _startId and _endId value. In this scenario, the function `convertOldGooey()` reverts and it is unclear which token ID value caused the issue. The owner will have to move through all values between the start and end values until the correct ID is excluded.
There is no check that the input values for `gooey1` and `gooey2` are not the same. The same tokenId cannot be used twice in `tumble()` function. Therefore the function `getTumbleCost()` should check that the input values are not the same.
For function `setTumbleFees()`, there is no check that the values for input `_tumbleBaseFee` and `tumbleScalingFee` do not go below or exceed certain values. This means that both values are unrestricted. Should the owner make a mistake on the input for these values, a user could potentially pay astronomical fees. The functions `updateGooTumbleWaitBlocks()`, `updateMaxNexus()` and `setDTokenDiscounts()` all have a similar issue in which the input values are not verified to be within a certain range. For `setDTokenDiscounts()`, if input `_dDiscountMultiplier` is a value higher than 100, then the user will receive no discount and in fact, have to pay more than the original `tumbleFee` calculated in the function `getTumbleCost()`. If `dDiscountMultiplier` is allowed to be value 0, then the user does not pay a tumble fee.
The contract `Gooeys.sol` is deployed without initializing the following state variables: `G_TOKEN`, `D_TOKEN` and `G_FOOD`. They will be initiated with address(0) as default. Between deploy and call to update the addresses through `loadExterns()`, there may be a span of time where logic could be corrupted.
In `Gooeys.sol`, one or more error messages related to require statements are not written to express the nature of the error when thrown.
The linked functions are equivalent to the compiler-generated getter functions for the respective variables.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (3)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething 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