Reality Cards contest - cmichel's results

The world's first 'outcome ownership' prediction market.

General Information

Platform: Code4rena

Start Date: 10/06/2021

Pot Size: $45,000 USDC

Total HM: 21

Participants: 12

Period: 7 days

Judge: LSDan

Total Solo HM: 13

Id: 13

League: ETH

Reality Cards

Findings Distribution

Researcher Performance

Rank: 3/12

Findings: 7

Award: $6,981.24

🌟 Selected for report: 7

πŸš€ Solo Findings: 3

Findings Information

🌟 Selected for report: axic

Also found by: JMukesh, a_delamo, cmichel, gpersoon, pauliax, s1m0, shw

Labels

bug
duplicate
3 (High Risk)

Awards

259.5075 USDC - $259.51

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

The ERC20.transfer() and ERC20.transferFrom() functions return a boolean value indicating success. This parameter needs to be checked for success. According to the standard the return value must be checked for true, otherwise the transfer will have failed.

The treasury contract calls ERC20.transferFrom/transfer four times without checking for the success return value.

Impact

ERC20-compliant tokens that don't actually perform the transfer and return false are still counted as a correct transfer. Users can "deposit" and increase their balance this way without actually transfering tokens, and then withdraw the treasury.

We recommend using OpenZeppelin’s SafeERC20 versions with the safeTransfer and safeTransferFrom functions that handles the return value check as well as non-standard-compliant tokens.

#0 - Splidge

2021-06-17T11:05:15Z

Duplicate of #2

#1 - dmvt

2021-07-11T12:38:03Z

duplicate of #2

Findings Information

🌟 Selected for report: pauliax

Also found by: 0xRajeev, cmichel, shw

Labels

bug
duplicate
3 (High Risk)

Awards

791.0609 USDC - $791.06

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

When a user deposits to the treasury they first approve the contract and then call its deposit action which performs an ERC20.transferFrom. It's possible for an attacker to frontrun the final deposit transaction after the user approval and turn the deposit into a sponsor this way, which is basically a free donation to a market outcome (card). The attacker can create their own market where they are the only participant betting on a card that is guaranteed to win.

Attack:

  1. Attacker creates custom market M with a guaranteed winning card. (They can time the market close in a way such that no other user can rent the card longer then them.)
  2. user A sends a transaction which approve the treasury for a deposit amount.
  3. user A sends a Treasury.deposit(amount) action instead.
  4. An attacker observes the deposit from 2 and frontruns it with a sponsor action from their custom market by sending a M.sponsor(A, amount) transaction.
  5. This increases the payout of the card in the attacker market but takes the funds from the user in the treasury.sponsor(_sponsorAddress=A, _amount) treasury call: erc20.transferFrom(_sponsor=A, address(this), _amount);
  6. The attacker wins, and can withdraw their profit at some point, essentially stealing the user's deposit

Impact

User deposits can be stolen.

The sponsor function should not allow specifying a custom sponsorAddress. The market should pull in the amounts from msg.sender using ERC20.transferFrom(msg.sender, amount) and then forward this amount to the treasury using a simple ERC20.transfer(treasury, amount) followed by the treasury.sponsor call that does the remaining book-keeping.

#0 - Splidge

2021-06-17T11:06:13Z

Duplicate of #40

#1 - dmvt

2021-07-11T12:39:50Z

duplicate of #40

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xRajeev

Labels

bug
2 (Med Risk)
sponsor confirmed
resolved

Awards

585.971 USDC - $585.97

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

The RCNftHubL2.safeTransferFrom function does not correctly implement the ERC721 spec:

When using safeTransferFrom, the token contract checks to see that the receiver is an IERC721Receiver, which implies that it knows how to handle ERC721 tokens. ERC721

This check is not implemented, it just drops the _data argument.

Impact

Contracts that don't know how to handle ERC721 tokens (are not an IERC721Receiver) can accept them but they should not when using safeTransferFrom according to spec.

Implement the IERC721Receiver check in safeTransferFrom.

#0 - Splidge

2021-06-21T15:44:57Z

This has been fixed while working on issue #118 commit here

Findings Information

🌟 Selected for report: cmichel

Labels

bug
2 (Med Risk)
sponsor confirmed
disagree with severity
resolved

Awards

1302.1578 USDC - $1,302.16

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

The treasury only checks its globalPause field but does not check its market-specific marketPaused field for Treasury.sponsor. A paused market contract can therefore still deposit as a sponsor using Market.sponsor

Impact

The market-specific pause does not work correctly.

Add checks for marketPaused in the Treasury for sponsor.

#0 - mcplums

2021-06-17T06:54:32Z

I don't think this is important but I guess it can't hurt to block sponsorship if paused

#1 - Splidge

2021-06-17T12:00:04Z

I'm not sure why this is a severity 2? Maybe it should be lower. Sponsoring a market, whether paused or not, doesn't come with an expectation to receive the funds back. So assets are not at risk here.

#2 - Splidge

2021-06-21T14:57:52Z

There have been changes made to marketPaused and how markets are created due to other issues that have been found. By default markets are now created in a paused state and it'd be useful to be able to sponsor them before the governors approve them. It's a nice thing for the sponsorship to be in place before anybody interacts with the contract. I have however made changes such that is the market pause is ever turned on by the Treasury owner then the sponsor function will revert. Changes here

Findings Information

🌟 Selected for report: cmichel

Labels

bug
2 (Med Risk)
sponsor disputed

Awards

1302.1578 USDC - $1,302.16

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

There are ERC20 tokens that may make certain customizations to their ERC20 contracts. One type of these tokens is deflationary tokens that charge a certain fee for every transfer() or transferFrom().

Impact

The deposit() function will introduce unexpected balance inconsistencies when comparing internal asset records with external ERC20 token contracts.

One possible mitigation is to measure the asset change right before and after the asset-transferring routines

#0 - mcplums

2021-06-17T06:38:17Z

I think balancedBooks modifier should handle this?

Of course it means we are unable to use such tokens, but that is ok

#1 - Splidge

2021-06-17T11:49:45Z

oh, trying the same one again..? 😁 https://github.com/code-423n4/2021-05-88mph-findings/issues/16

I'll fight this one though, I'd argue that we are using ERC20 tokens and according to the ERC20 spec for transferFrom:

Transfers _value amount of tokens from address _from to address _to

A deflationary token therefore isn't compliant to ERC20 as it doesn't transfer the full _value and so it isn't what we are planning to use and not relevant here.

#2 - dmvt

2021-07-10T17:50:57Z

If you plan not to support these tokens it should be very clearly documented. Keep in mind that "we don't support that" still has massive impact on the users involved. See: imBTC / ERC777 on Uniswap v1. The issue is valid and should stand in the audit report, in part so that future users see it.

Findings Information

🌟 Selected for report: cmichel

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

1302.1578 USDC - $1,302.16

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

The treasury implements a max contract balance check in the deposit function:

require(
    (erc20.balanceOf(address(this)) + _amount) <= maxContractBalance,
    "Limit hit"
);

A whale can stop anyone from depositing by front-running a user's deposit with a deposit that pushes the contract balance to the maxContractBalance limit first. The user's deposit will then fail in the check. Afterwards, the whale can withdraw again.

This is not only restricted to whales, miners/users can do the same using same-block cross-transaction flashloans and submitting a (attacker deposit, user deposit, attacker withdraw) flashbundle to a miner. Possibilities like this will only become more prevalent in the future.

Impact

Any users can be blocked from depositing which prevents them from renting cards. This allows an attacker to manipulate the outcome of a market in their favor by strategically preventing other competitors to bid on their cards (causing forfeiture due to a low deposit balance).

Remove the contract limit or at least set the limit very high if it keeps happening.

#0 - mcplums

2021-06-17T06:32:43Z

This is a good one- but I don't think we need to make any changes to the contract. We can use it as originally intended, then if it is exploited as above, we can switch to only setting the variable to 0 or maxuint256. So it just acts as a toggle on whether deposits are allowed.

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: JMukesh, cmichel, jvaqa

Labels

bug
duplicate
1 (Low Risk)
sponsor confirmed
resolved

Awards

79.1061 USDC - $79.11

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

On failure, many functions fail with an assert which consumes all available gas of the transaction. A best practice is to only use assert for tautologies and checks that one never expects to be false. (like invariants)

Occurences:

  • setReferenceContractAddress
  • Market.initialize
  • Market._transferCard
  • Market._payout
  • Treasury.payout

Impact

No gas is refunded to the user if they used any of the functions incorrectly

Use require instead of assert.

#0 - Splidge

2021-06-17T11:32:27Z

This covers a bunch of other issues which I already considered to be duplicates. I've marked all of them a duplicate of this one.

#1 - Splidge

2021-06-18T10:24:35Z

This most recent duplicate #83 appears to be the most complete one, however for simplicities sake I'll not go changing them all around right now. It actually considers the compiler version we are using to correctly state that the remining gas isn't consumed, as some other issues incorrectly stated. It also seems to cover every instance in the one report.

#2 - Splidge

2021-06-21T15:26:23Z

Most of these were fixed here Some extras found in issue #83 have been fixed here Although a number were left as they are correctly using assert()

#3 - dmvt

2021-07-11T10:26:39Z

duplicate of #83

Findings Information

🌟 Selected for report: JMukesh

Also found by: 0xRajeev, a_delamo, cmichel, maplesyrup

Labels

bug
duplicate
1 (Low Risk)

Awards

56.9564 USDC - $56.96

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

Some parameters of functions are not checked for invalid values:

  • RCNftHubL2.constructor: address _factoryAddress, address childChainManager
  • RCTreasury.constructor: address _tokenAddress

Impact

A wrong user input or wallets defaulting to the zero addresses for a missing input can lead to the contract needing to redeploy or wasted gas.

Validate the parameters.

#0 - Splidge

2021-06-17T12:36:34Z

Duplicate of #56

#1 - dmvt

2021-07-11T10:36:27Z

duplicate of #56

Findings Information

🌟 Selected for report: cmichel

Labels

bug
1 (Low Risk)
sponsor confirmed
resolved

Awards

434.0526 USDC - $434.05

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

The Factory.createMarket iterates over all _cardAffiliateAddresses.

Impact

The transactions can fail if the arrays get too big and the transaction would consume more gas than the block limit. This will then result in a denial of service for the desired functionality and break core functionality.

Perform a _cardAffiliateAddresses.length == 0 || _cardAffiliateAddresses.length == tokenUris.length check in createMarket instead of silently skipping card affiliate cuts in Market.initialize. This would restrict the _cardAffiliateAddresses length to the nftMintingLimit as well.

#0 - Splidge

2021-06-21T15:15:57Z

implemented here

Findings Information

🌟 Selected for report: cmichel

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

434.0526 USDC - $434.05

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

The uberOwner cannot do the same things the owner can. They can "only" set the reference contract for the market.

The same ideas apply to Treasury and Factory's uberOwner.

Impact

The name is misleading as it sounds like the uber-owner is more powerful than the owner.

Uberowner should at least be able to set the owner if not be allowed to call all functions that an owner can. Alternatively, rename the uberOwner.

#0 - mcplums

2021-06-17T06:34:11Z

I like this! Is not too important, but can't hurt to have uber owner able to change the owner.

#1 - Splidge

2021-06-21T15:42:48Z

I will come back to this issue if time allows. Ownable.sol has been made such that you can't override transferOwnership() or the onlyOwner modifier. This means the next best option would be changing to AccessControl.sol which is more effort than I think the benefit warrants given our current timescale.

Findings Information

🌟 Selected for report: cmichel

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

434.0526 USDC - $434.05

External Links

Handle

cmichel

Vulnerability details

Vulnerability Details

Usually one tries to avoid toggle functions in blockchains, because it could be that you think that the first transaction you sent was not correctly submitted (but it's just pending for a long time), or you might even be unaware that it was already sent if multiple roles can set it (like with changeMarketApproval / onlyGovernors) or if it's an msig. This results in potentially double-toggling the state, i.e, it is set to the initial value again.

Some example functions: changeMarketCreationGovernorsOnly, changeMarketApproval, and the ones that follow.

Impact

The outcome of toggle functions is hard to predict on blockchains due to the very async nature and lack of information about pending transactions.

Use functions that accept a specific value as a parameter instead.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter