RealityCards v2

February 19, 2021

1. Preface

The team of RealityCards contracted byterocket to conduct a smart contract audit of RealityCards V2. RealityCards is developing a novel prediction and betting market based on NFTs. In their latest update, they introduced various new features, such as an order book-based rent mechanism and a treasury that handles the financial part.

The team of byterocket reviewed and audited the above smart contracts in the course of this audit. We started on the 02nd of February and finished on the 19th of February.

The audit included the following services:

  • Manual Multi-Pass Code Review
  • Automated Code Review
  • In-Depth Protocol Analysis
  • Deploy & Test on our Testnet
  • Formal Report

byterocket gained access to the code via their private GitHub repository. We initially based the audit on the master branch’s state on January 04th, 2021 (commit hash a4be07c68f9c643324f851cf6562cf79128eda6d). There has been an additional commit (commit hash 8c0b05b25a7deef25f98532ae2f8afd4f9a84360) directly after our start, containing a fix to a bug that the developers have discovered.

2. Manual Code Review

We conducted a manual multi-pass code review of the smart contracts mentioned in section (1). Three different people went through the smart contract independently and compared their results in multiple concluding discussions.

These contracts are written according to the latest standards used within the Ethereum community and the Solidity community’s best practices. The naming of variables is very logical and understandable, which results in the contract being useful to understand. The code is very well documented in the code itself.

A document containing an extensive description of the code and its specifications has been handed over to us prior to the start of our audit. This document has been beneficial in assessing the different functions of the implementation.

On the code level, we found two medium severity and two low severity bugs or flaws. A further check with multiple automated reviewing tools (MythX, Slither, Manticore, and different fuzzing tools) did not find any additional bugs besides some common false positives.

2.1. Bugs & Vulnerabilities

A) - RCFactory.sol - Line 312  [LOW SEVERITY]
if (advancedWarning != 0) {
 require(_timestamps[0] >= now, "Market opening time not set");
 require(_timestamps[0].sub(advancedWarning) > now, "Market opens too  soon" );

If an advancedWarning is set (i.e., the value is non-zero), there are two requires that check whether a market satisfies this requirement. However, the first require in line 321 is either in the wrong place (should be outside of the if-clause to validate whether the opening time is set and not in the past) or - if the behavior of markets that started in the past is desired - unnecessary, in which case line 313 is already sufficient as a market can not be in the past if its starting time minus the advancedWarning variable is still greater than the current timestamp.

B) - RCTreasury.sol - Line 208  [MEDIUM SEVERITY]
function payout(address _user, uint256 _dai)

The function does not check whether globalPause is set, which does not comply with the provided specification, where it is stated that setting the globalPause to true should prevent deposits, withdrawals as well as rentals.

C) - RCTreasury.sol - Line 219  [LOW SEVERITY]
function sponsor()

The function does not check whether globalPause or marketPaused is set, which might not comply with the provided specification, where it is stated that setting the globalPause to true should prevent deposits, withdrawals as well as rentals. This is dependent on whether the developers intended this function to work still; however, we would classify it as a deposit.

D) - RCTreasury.sol - Line 226  [MEDIUM SEVERITY]
function processHarbergerPayment(address _newOwner, address _currentOwner, uint256 _requiredPayment)

The function does not check whether globalPause or marketPaused is set, which might not comply with the provided specification, where it is stated that setting the globalPause to true should prevent deposits, withdrawals as well as rentals. It is also stated that setting marketPaused to true shall prevent rentals, which doesn’t seem to be the case here.

2.2. Other Findings & Remarks

A) - RCMarket.sol - Line 525-529  [NO SEVERITY]
if (_timeHeldLimit == 0) {
  _timeHeldLimit = MAX_UINT128; // so 0 defaults to no limit

uint256 _minRentalTime = uint256(1 days).div(minRentalDivisor);
require(_timeHeldLimit >= timeHeld[_tokenId][msgSender()].add(_minRentalTime), "Limit too low");

These lines are only necessary if timeHeldLimit != 0, so adding an else branch to the if-clause above, containing lines 528 and 529, would save anyone removing their limit some gas. However, the savings are probably negligible on the xDai chain.

B) - RCPRoxyMainnet.sol - Line 40  [NO SEVERITY]
bool depositsEnabled = true;

There is no visibility set, which reverts to internal. This might be desired, but we suggest stating it explicitly in that case.

3. Protocol/Logic Review

Part of our audits are also analyses of the protocol and its logic. A team of three auditors went through the implementation and documentation of the implemented protocol.

Treasury-based Approach

In contrast to the first version of the RealityCards smart contracts, all of the xDai related actions are now facilitated through a treasury contract. This not only reduces the attack surface but also allows for a way better UX. It shifts the security model of the whole system a bit towards a more central approach, where a potential attack would influence all of the markets simultaneously, which is (in our opinion) outweighed by the benefits that it provided. The development team has implemented various security measures to protect the contract, which we deem sufficient at this point.

Cross-Chain Communication

As the RealityCards implementation migrated from the Ethereum Mainnet to the xDai chain earlier last year, the communication between these two networks is always somewhat of a specialty. We have seen many projects suffering from problems when cross-chain bridges are (for some arbitrary reason) failing our suffering from an outage. We generally advise all of our clients to assume that every bridge call will fail and keep this in mind when developing with bridges. The development team of RealityCards has done a great job in doing so - all of the essential functions can be called multiple times in case of bridge errors or outages. The receiving side will always accommodate numerous calls.


The team of RealityCards followed a very widely-accepted approach in implementing their Meta Transaction mechanism, which allows users to execute gasless transactions and interact with the platform without needing to have the xDai chain enabled in their wallet. We were not able to identify problems that this would cause.


We could not find any weaknesses or underlying problems of a game-theoretic nature in the protocol and its logic.  We were also not able to think of any loopholes that the RealityCards developers did not cover.

4. Testnet Deployment/Logic Review

As per our testing strategy, we deploy audited smart contracts (if requested by the client) onto a testnet to verify the implementation’s functionality. We usually deploy simple smart contracts to a local Ganache instance, which is sufficient for most cases. In this case, given the contracts’ complexity, we wanted to ensure no Ganache-related coverups of any misbehavior of the contracts. We created two testnets: a geth-based one and a parity/openethereum-based one. All of our tests have been executed on both testnets without any difference in the results. We were able to use the contracts as intended and could not maliciously game the protocol in practice.

We used fuzzing tools that generate random and semi-random inputs and interact with the contracts, trying to produce any unforeseen states within the contracts, especially the treasury contract. Throughout our tests, we were not able to create or observe any issues or problems. The contract behaved as expected and reverted correctly, given wrong inputs.

5. Summary

During our code review (which was done manual and automated), we found two medium severity and two low severity bugs or flaws. Some very minor remarks are noted in section (2.2). Additionally, our automated systems and review tools also did not find any additional ones.

The review and analysis of the protocol did neither uncover any game-theoretical nature problems nor any other functions prone to abuse.

During our multiple deployments to various local testnets we haven’t been able to find any additional problems or unforeseen issues.

In general, we are delighted with the overall quality of the code and its documentation.

Stored on IPFS

We store our public audit reports on IPFS; a peer-to-peer network called the "Inter Planetary File System". This allows us to store our reports in a distributed network instead of just a single server, so even if our website is down, every report is still available.

Learn more about IPFS
Signed On-Chain

The IPFS Hash, a unique identifier of the report, is signed on-chain by both the client and us to prove that both sides have approved this audit report. This signing mechanism allows users to verify that neither side has faked or tampered with the audit.

Check the Signatures