# Security Audit of `GameContract` contracts. ## Conclusion This audit was made by Auditor: Vladimir Smelov <vladimirfol@gmail.com>. Date: 2023-06-12 TODO ## Scope TODO ## Methodology 1. Blind audit. Understand the structure of the code without reading any docs. 2. Ask questions to developers. 3. Run static analyzers. 4. Find problems with: - backdoors; - bugs; - math; - potential leaking of funds; - potential locking of the contract; - validate arguments and events; - others. ## Result #### CRITICAL-1. At - Contract.sol:257 ```solidity=257 bytes32 bet = encryptedBet ^ key; ``` key could be changed to get any value nonce := getSignature -> hash(secret, gameId) any device can calc nonce begin: hash(bet, nonce) = proof -> SC finalize: hash(0, nonce) ?= proof hash(1, nonce) ?= proof finalize(0, nonce) hash(0, nonce) == proof ##### INFORMAL приведу пример CoinSide = Heads = 2 = 0b10 CoinSide = Tails = 3 = 0b11 encryptedBet = 0b00 если загадывающий хочет он может дать key = 0b01 -> encryptedBet ^ key = 0b10 key = 0b00 -> encryptedBet ^ key = 0b11 смысл этого тоже непонятно if (coinSide != CoinSide.Heads && coinSide != CoinSide.Tails) { return CoinSide.Closed; } как может расшифровка секрета о том какой сайд монеты загадан дать чтото кроме орла или решки? но с encryptedBet ^ key может дать что угодно Давайте вместо xor рассмотрим использование add (На самом деле с точки зрения криптостойкости они одинаковые) Secret + key = result Secret = 1 Вы думаете что знаете моё решение Дальше я такой думаю окей я хочу чтобы мой result=1, then key I will give =0, otherwise if I want the result to become 0 I will give key=-1 Примерно то же самое Xor это совсем не необратимая функция мягко говоря Поэтому я и предлагал что Secret = ecdsaSignature(result) ECDSA signatures are designed to be computationally infeasible to reverse. This is a fundamental property of secure cryptographic systems: they are "one-way" functions. То же самое можно сделать используя secret=keccak256(result, nonce) ##### Status. NEW ______ #### MAJOR-1. At - Contract.sol:128 ```solidity=128 feeReceiver.transfer(value); ``` owner can break contract anytime by setting `feeReceiver` to the contract which does not accept incoming ETH transfers (with no `receive() external payable`). Or use call and check success += if no success ##### Status. NEW ______ #### MAJOR-2. At - Contract.sol:358 ```solidity=358 function _withdrawEarnings(address receiver, uint256 value, bytes32 gameId) internal { ``` do pull-pattern, something like: https://medium.com/heartbankstudio/smart-contract-design-patterns-8b7ca8b80dfb https://docs.openzeppelin.com/contracts/4.x/api/security#PullPayment ##### Status. NEW ______ #### WARNING-1. At - ContractTron.sol:10 ```solidity=10 contract GameContractTron is Ownable, ReentrancyGuard { ``` avoid code-duplication, you can use Contract with no proxy if you want non-upgradeable contract ##### Status. NEW ______ #### WARNING-2. At - Contract.sol:14 ```solidity=14 enum CoinSide { None, Closed, Heads, Tails, NotOpenedButPaymentRequested } ``` this is contr-intuitive. CoinSide could be Heads or Tails, consider the usage of another variable for clarity. ##### Status. NEW ______ #### WARNING-3. At - Contract.sol:100 ```solidity=100 _playerReferers[_msgSender()] = referer; ``` consider adding setReferrer onlyOwner function, to resolve tricky situations. For example, referrer as a human may stop to use specific wallet and move to another one. For security you can approve re-setting the referrer by both, user and owner. ##### Status. NEW ______ #### WARNING-4. At - Contract.sol:123 ```solidity=123 payable(_msgSender()).transfer(earnings); ``` - Contract.sol:128 ```solidity=128 feeReceiver.transfer(value); ``` never use transfer - https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ ##### Status. NEW ______ #### WARNING-5. At - Contract.sol:205 ```solidity=205 _games[id].totalGainings *= 2; ``` consider adding a comment how is totalGainings set to avoid misunderstanding. ##### Status. NEW ______ #### WARNING-6. At - Contract.sol erc20/erc721 accidentally sent to the contract will be lost. Consider add recoverERC20, recoverERC721 methods. Take a look - https://github.com/vittominacori/eth-token-recover ##### Status. NEW ______ #### WARNING-7. At - Contract.sol:231,244,311 ```solidity=231 return; ``` ```solidity=244 return; ``` ```solidity=311 return; ``` Better to raise an error to prevent misusing. ##### Status. NEW ______ #### WARNING-8. At - Contract.sol:335 ```solidity=335 serviceEarnings = totalGainings * _serviceFee / (2 * DENOMINATOR); ``` explain why divided by 2 write detailed comment ##### Status. NEW ______ #### LOW-1. At - Contract.sol:2 ```solidity=2 pragma solidity 0.8.6; ``` rename the file to GameContract.sol ##### Status. NEW ______ #### LOW-2. At - Contract.sol:3 ```solidity=3 pragma experimental ABIEncoderV2; ``` activated by default https://docs.soliditylang.org/en/v0.8.0/080-breaking-changes.html ##### Status. NEW ______ #### LOW-3. At - Contract.sol:16-28 ```solidity=16 struct Game { address playerOne; bytes32 playerOneEncryptedBet; CoinSide playerOneChosenSide; address playerTwo; bytes32 playerTwoEncryptedBet; CoinSide playerTwoChosenSide; uint256 totalGainings; GameStatus status; uint256 gameCreatedTimestamp; uint256 gameStartedTimestamp; address winner; } ``` re-organise for efficient packing - https://dev.to/javier123454321/solidity-gas-optimizations-pt-3-packing-structs-23f4 ##### Status. NEW ______ #### LOW-4. At - Contract.sol:30 ```solidity=30 uint256 constant DENOMINATOR = 10_000; ``` explicitly declare as public/internal ##### Status. NEW ______ #### LOW-5. At - Contract.sol:39 ```solidity=39 mapping (address => uint256) private _refererEarings; ``` typo in Earnings ##### Status. NEW ______ #### LOW-6. At - Contract.sol:40 ```solidity=40 mapping (address => address) private _playerReferers; ``` typo in Referrers ##### Status. NEW ______ #### LOW-7. At - Contract.sol:51-56 ```solidity=51 event GameCreated(bytes32 id, address indexed playerOne, uint256 indexed bidValue); event GameStarted(bytes32 id, uint256 startedTimestamp, address indexed playerOne, address indexed playerTwo, uint256 indexed totalGainings); event SecretOpened(bytes32 id, address indexed player, CoinSide coinSide); event GameFinished(bytes32 id, address indexed winner, address indexed looser, uint256 indexed gainings); event GameCancelled(bytes32 id); event GamePayout(bytes32 id, address indexed receiver, uint256 value); ``` indexed id? ##### Status. NEW ______ #### LOW-8. At - Contract.sol:58 ```solidity=58 event RefererAssesed(address indexed referer, uint256 value); ``` typo in Assessed ##### Status. NEW ______ #### LOW-9. At - Contract.sol:59 ```solidity=59 event BidOpeningFailed(address indexed sender, bytes32 gameId, string message); ``` indexed gameId ##### Status. NEW ______ #### LOW-10. At - Contract.sol:71 ```solidity=71 require(_feeReceiver != address(0), Errors.ZERO_ADDRESS); ``` put inside setFeeRecevier, otherwise if _feeReceiver is not valid, the contract will be locked. ##### Status. NEW ______ #### LOW-11. At - Contract.sol:86 ```solidity=86 ) external payable nonReentrant { ``` unclear function semantics, consider documenting, what is id what is key ##### Status. NEW ______ #### LOW-12. At - Contract.sol:94 ```solidity=94 function placeBid( ``` rename to placeBidWithReferer ##### Status. NEW ______ #### LOW-13. At - Contract.sol:97 ```solidity=97 ) external payable nonReentrant { ``` unclear function semantics, consider documenting, what is id what is key ##### Status. NEW ______ #### LOW-14. At - Contract.sol:99 ```solidity=99 if (_playerReferers[_msgSender()] == address(0)) { ``` there is no need to use _msgSender() use msg.sender ##### Status. NEW ______ #### LOW-15. At - Contract.sol:131 ```solidity=131 function setFeeRecevier(address payable newReceiver) public onlyOwner { ``` typo in word receiver ##### Status. NEW ______ #### LOW-16. At - Contract.sol:132 ```solidity=132 feeReceiver = newReceiver; ``` what if newReceiver is not a valid account receiving fees? Consider the usage of push-pull pattern for fees to avoid contract locking. ##### Status. NEW ______ #### LOW-18. At - Contract.sol:137 ```solidity=137 _serviceFee = newValue; ``` limit max value ##### Status. NEW ______ #### LOW-19. At - Contract.sol:142 ```solidity=142 _refererFee = newValue; ``` limit max value ##### Status. NEW ______ #### LOW-20. At - Contract.sol:147 ```solidity=147 _lowestBid = newValue; ``` restrict low/high values ##### Status. NEW ______ #### LOW-21. At - Contract.sol:152 ```solidity=152 _highestBid = newValue; ``` restrict low/high values ##### Status. NEW ______ #### LOW-22. At - Contract.sol:157 ```solidity=157 _waitingForOpenBidSeconds = newValue; ``` restrict low/high values ##### Status. NEW ______ #### LOW-23. At - Contract.sol:162 ```solidity=162 _minRefererPayoutValue = newValue; ``` restrict low/high values ##### Status. NEW ______ #### LOW-24. At - Contract.sol:193 ```solidity=193 _games[id].totalGainings = msg.value; ``` it's not clear why the bid of the user goes to the totalGainings ##### Status. NEW ______ #### LOW-25. At - Contract.sol:213,220,223 ```solidity=213 for (uint i = 0; i < ids.length; i++) { ``` ```solidity=220 for (uint i = 0; i < ids.length; i++) { ``` ```solidity=223 for (uint256 i = 0; i < expiredIDs.length; i++) { ``` use unchecked declaration to save gas ##### Status. NEW ______ #### LOW-26. At - Contract.sol:218 ```solidity=218 function openBid(bytes32[] memory ids, bytes32[] memory keys, bytes32[] memory expiredIDs) public nonReentrant{ ``` use other name ##### Status. NEW ______ #### LOW-27. At - Contract.sol:300 ```solidity=300 _games[id].status = GameStatus.Paid; ``` maybe create another status ##### Status. NEW ______ #### LOW-28. At - Contract.sol:251 ```solidity=251 if (_games[id].playerOneChosenSide > CoinSide.Closed && _games[id].playerTwoChosenSide > CoinSide.Closed) { _finishGame(id); } ``` This is unclear place. Consider documenting. ##### Status. NEW ______ #### LOW-29. At - Contract.sol:285 ```solidity=285 } ``` What will happen in case of `else` condition??? ##### Status. NEW