Augur Core v2 Audit: Components

The Augur team asked us to review and audit a number of components of the Augur Core v2 project. We looked at the code and now publish our results.

The audited commit is 67c0fec95e65fca8d27751f28a6c06e5247978c6 and only the following Solidity smart contracts were included in the audit’s scope:

The audit was primarily focused on:

  • Augur’s integration with Gnosis wallets via the GnosisSafeRegistry contract. Note that Augur’s GnosisSafe.sol file was not audited, though it was compared against official, known, Gnosis Safe Multisig wallet implementations to check for compliance.
  • Augur’s novel recurring market for the Warp Sync feature.
  • The revamped affiliates system.
  • Augur’s implementation of EIP 1155. Note that we did not audit the EIP on its own, but only compared Augur’s implementation against the official specification.

Additionally, we included the Market contract for completeness, as it suffered changes related to the audited features (mostly after the introduction of the new affiliates system). Yet changes related to other integrations, such as DAI, were not considered at all during the audit.

Update: Many of the issues have been addressed or accepted by the Augur team. Our analysis of the mitigations assumes the pull requests will be merged, but disregards any other potential changes to the code base as well as any changes that do not affect the audited functionality.

Next, our audit assessment and recommendations, in order of importance.

Critical severity

[C01] Anyone can register backdoored wallets for any owner in GnosisSafeRegistry contract

The GnosisSafeRegistry contract is intended to be a single on-chain registry to keep track of Augur users’ Gnosis Safe Multisig wallets. The registry is expected to be called upon creation of a Gnosis Safe Multisig so that Augur can quickly look up and verify a user’s wallet. GnosisSafeRegistry allows users to register one or more wallets through the register and callRegister functions. Calls to these functions are expected to always be executed from a user-controlled Gnosis Safe Multisig wallet. Wallets are stored in the accountSafes mapping under the first address listed in the owners array of the GnosisSafe contract. As a consequence, a single owner can have multiple wallets registered in Augur’s GnosisSafeRegistry.

GnosisSafeRegistry is implemented as a registry of proxy contracts pointing to a single known, legitimate, implementation of GnosisSafe. All registered proxies must:

Additionally, to further restrict the type of valid wallets, all registered wallets can only have a single owner and a threshold equal to 1.

In spite of all described restrictions, it is possible for an attacker to register malicious Gnosis Safe Multisig wallets under any owner address. By leveraging Gnosis Safe modules, an attacker can craft, deploy and register Gnosis wallets with any owner address and modules attached. Complying with all requirements programmatically enforced by Augur’s registry, these wallets would appear to be legitimate and victim-controlled, but would actually contain a malicious backdoor that would give the attacker unrestricted control over it. This undermines the fundamental assumption of the GnosisSafeRegistry contract, thus it should be considered a critical vulnerability. A step-by-step proof-of-concept exploit of this vulnerability can be found in this private repository.

At least two sensitive attack vectors have been identified:

  • For a user that has never registered a wallet in the registry: an attacker can simply register a wallet with the victim’s address as its owner and perform actions through the wallet. To minimize unexpected side-effects, they could front-run a legitimate transaction intending to register a wallet. Upon querying the registry with the getSafe function, the victim would see a valid wallet registered under her address (and she would even be its sole owner). However, this wallet would have attached an attacker-controlled module that would grant the attacker unrestricted privileges over it. Thus, as module actions do not require owners’ signatures, the attacker would be able to trigger any kind of transactions from the wallet without requiring confirmation from its owner (i.e., the victim).
  • For a user that has already registered a wallet in the registry: an attacker could deploy and register a wallet with the victim’s address as its owner. This wallet would be registered under the victim’s address in the registry, but would end up in the queue of the victim’s registered wallets. Only after de-registering her first wallet would the victim find out that there is another wallet registered under her address. Were she to start using it (as she would be its listed owner), the attacker would be able leverage the backdoor to execute malicious actions from it.

In both cases the attack leverages the fact that Augur’s registry tracks wallets under their owner addresses. As explained, in Gnosis Safe Multisig wallets with malicious modules attached, addresses listed as owners can be easily controlled by an attacker. Consider implementing further restrictions to prevent any attacker to register backdoored wallets for any owner address in Augur’s registry. As starting points to be further analyzed by Augur’s development team, we suggest:

  • Further restricting the type of Gnosis Safe Multisig wallets that can be registered to only those without modules attached. This property can be checked by querying the getModules public getter.
  • Ensuring the caller of the registry’s register and callRegister functions is the address listed in the owners array of the GnosisSafe contract. This can be checked by requiring the owner’s signature or a transaction signed by the owner address.

Update: Fixed in commit 7787dba1. Now only Gnosis Safe Multisig wallets deployed with known parameters and no modules can be registered.

High severity

[H01] Malicious market can arbitrarily decide when to pay affiliate fees

It is possible for any user to create a malicious Augur market that can arbitrarily decide when to pay affiliate fees (including the 20% that should be directed to the source trader account).

Any attacker can create a Market through the createMarket function of the MarketFactory contract. The _affiliateValidator parameter may be the address of an attacker-controlled contract implementing the IAffiliateValidator interface. This address is then set during initialization of the Market contract without any kind of validation.

Once the recordMarketCreatorFees function of the Market contract is called, the Market contract executes a call to the getAndValidateReferrer function of the Affiliates contract, passing the attacker-controlled affiliateValidator address as the second argument. In turn, the getAndValidateReferrer function will attempt to call a validateReference function in the attacker-controlled affiliateValidator address, providing the two accounts as parameters. At this point, the attacker is in full control of the execution and can arbitrarily determine whether or not the call is successful. If it is not, then the getAndValidateReferrer function will return the zero address. As a consequence, back in the Market contract, the _affiliateAddress local variable will be set to zero, which will prevent paying out the trading fees to the _sourceAccount and accumulating affiliate fees.

It must be stressed that the decision of if and when to pay fees does not currently depend on the Augur protocol at all, but rather on arbitrary logic chosen by the creator of the market in the affiliateValidator address.

Trying to prevent this attack by limiting the gas forwarded to the potentially malicious AffiliateValidator contract does not seem to be a complete solution. Consider putting more strict limitations on the kind of affiliate validator contract that a market creator can set. Ultimately, consider entirely disallowing market creators to set potentially malicious contracts in a market’s affiliateValidator address, and instead deploying and setting legitimate AffiliateValidator contracts programmatically during a Market initialization.

Update: Fixed in commit 8e62ffe8. Anyone can create a new AffiliateValidator instance using the known contract template, and the Market contract ensures the specified validator is one of these instances.

[H02] Market creators can avoid paying 20% cut of affiliate fees to traders

The recordMarketCreatorFees function of the Market contract determines under which circumstances a portion of the market’s creator fees gets distributed to affiliates. When there is an affiliate associated with the _sourceAccount address, the fingerprint of the affiliate’s address is compared against the fingerprint passed as an argument. If both fingerprints match, then the affiliate’s address is set to zero. As a result, no fees are paid to the affiliate nor the source account (who is expecting to receive 20% of the affiliate’s fees). These fees are taken from the total amount of market creator fees.

When a user trading in a market has been referred by the market creator, then the market creator should receive the corresponding affiliate fees from the user’s trading operations. In this case, to avoid paying 20% of the affiliate fees to the user, the market creator can execute front-running transactions that will cause the following trading operation to pay zero fees to affiliates. Specifically, the market creator should:

  1. See in advance which fingerprint the user set when submitting the trading transaction.
  2. Front-run with a call to the setFingerprint function, aiming to change her account’s fingerprint to the one used by the user.

If the front-run succeeds, then the stored fingerprint and the one submitted by the user should match. As a consequence, the execution of recordMarketCreatorFees will not pay the 20% of the affiliate fees to the user (because the execution would never jump into the if block in lines 347 to 355). Note that this attack should only be profitable for a market creator when the amount to be paid to the user (i.e., 20% of the affiliate fees) is greater than the cost of submitting the front-running transaction to change the fingerprint.

This issue is related to another issue already anticipated by Augur: the fingerprint scheme is intended to prevent users from registering a different address that they control as their affiliate, but it can be easily bypassed by simply changing the fingerprint to a unique value per address. Any solution that can effectively prevent Sybil attacks (where individual users can gain an advantage by controlling multiple accounts) would typically involve identity-based schemes, such as the KYC process intended to be used with the AffiliateValidator contract.

Since the fingerprint protection can be easily bypassed and it also introduces the vulnerability described here, consider removing the fingerprint mechanism entirely.

Alternatively, one plausible course of action to be analyzed is restricting the amount of times an account can modify its fingerprint in the Affiliates contract. More importantly, if there are no reasons why an account should ever update its fingerprint, consider reverting all calls to the setFingerprint function if the caller has already registered a fingerprint in the contract.

Update: Augur has decided to accept this risk because the market creator already chooses an affiliate validation contract, as well as the market creator fees that are distributed. While we understand Augur’s stance, we keep our reservations on whether this risk should be accepted, considering that it allows market creators to avoid paying fees owed to individual traders.

[H03] Affiliate keys can be stolen

The addKey function of the AffiliateValidator contract is used to associate a key with a user account. The key is expected to be derived from the user’s KYC information. The function first confirms that a given key-salt pair was signed by an operator and that the salt has not been used already. Then it associates the key with the sender’s account.

However, the signature is not cryptographically tied to the caller’s address (i.e., the msg.sender address). This means that an attacker can obtain the signature before it is confirmed (e.g., by reading it in a node’s public pool of pending transactions), and later call the addKey function to associate the stolen key with the attacker’s account. This will allow the attacker to register a valid key without going through the mandatory KYC process. Furthermore, it will also prevent the victim from using the signature.

Consider modifying the addKey function of the AffiliateValidator contract to include the user’s address within the key hash, so as to ensure the signature can only be redeemed by the intended account.

Update: Fixed in PR #5564. The user’s address is included within the key hash.

[H04] User can unknowingly contribute to the wrong market outcome in forked universe

The Market contract has a preemptiveDisputeCrowdsourcer variable that allows users to pool funds in support of the tentative winning outcome in anticipation of a dispute. If a dispute is successful, it becomes the new tentative winning outcome and the preemptiveDisputeCrowdsourcer, if it is non-zero, becomes a counter-dispute contract in favor of the previous outcome.

However, it is possible for the preemptiveDisputeCrowdsourcer to (inconsistently) support an outcome that is not the current tentative winning outcome. In particular, when a different market in Augur undergoes a fork and disavowCrowdsourcers is called on the non-forking market, the dispute chain is reset to the initial reporter but the preemptiveDisputeCrowdsourcer is not reset. While the initial reporter becomes the tentative winning outcome, it may have a different payout distribution hash to the preemptiveDisputeCrowdsourcer.

In this scenario, should a user call the contributeToTentative function with the correct payout numerators corresponding to the tentative winning outcome, the function will eventually call internalContribute with overload set to true. As a consequence, the internal call to getOrCreateDisputeCrowdsourcer will retrieve the (inconsistent) preemptiveDisputeCrowdsourcer, and the user funds will be incorrectly added to this outcome.

Consider resetting the preemptiveDisputeCrowdsourcer in the disavowCrowdsourcers function.

Update: Fixed in PR #5564. The preemptiveDisputeCrowdsourcer state variable is now is reset in the disavowCrowdsourcers function.

Medium severity

[M01] Affiliate keys can be reused

The addKey function of the AffiliateValidator contract is used to associate a key with a user account. The key is expected to be derived from the user’s KYC information. The function first confirms that a given key-salt pair was signed by an operator and that the salt has not been used already. Then it associates the key with the sender’s account.

However, should the signer be a registered operator on multiple instances of the same AffiliateValidator contract, any user can take a signature intended for one contract and apply it to any other. As a consequence, any user would be able to obtain valid keys without going through the mandatory KYC process.

Depending on the expected scenarios where multiple AffiliateValidator contracts share operators, it may be acceptable for the same user to reuse a key (assuming the reported “[H03] Affiliate keys can be stolen” issue is first addressed). Otherwise, consider including the contract’s address within the key hash to ensure the signature can only be used with the expected AffiliateValidator contract.

Update: Fixed in PR #5564. The AffiliateValidator contract address is included within the key hash.

[M02] Total amount of affiliate fees not decreased after withdrawal

The withdrawAffiliateFees function of the Market contract allows anyone to trigger a withdrawal of all fees an affiliate has earned. While the corresponding affiliate fees are correctly set to zero, the total amount of affiliate fees (tracked in the totalAffiliateFeesAttoCash state variable) is never decreased. As a result, all Augur markets can end up in an inconsistent state where the sum of all entries in the affiliateFeesAttoCash mapping does not exactly add up to totalAffiliateFeesAttoCash.

This issue does not pose a security risk for the Augur protocol. Still, consider always decreasing the totalAffiliateFeesAttoCash after a withdrawal of affiliate fees to ensure consistency and avoid breaking a relevant invariant of the Market contract.

Update: Fixed in PR #5564. This is actually the intended behavior so totalAffiliateFeesAttoCash has been renamed to totalPreFinalizationAffiliateFeesAttoCash for clarity.

[M03] WarpSync contract may return erroneous finalization reward

The public getFinalizationReward function of the WarpSync contract is intended to retrieve the finalization reward of a given market. Internally, it calls the getRepReward private function passing the end time of the market’s dispute window as an argument.

If getFinalizationReward is called when the market’s dispute window is not yet over (i.e., when block.timestamp is lower than _market.getDisputeWindow().getEndTime()), then the unsafe arithmetic operation in line 77 will inevitably underflow. As a result, the returned amount of REP representing the finalization reward will be erroneous.

Note that this issue does not introduce a security risk on its own. The only time the getFinalizationReward function is used internally by Augur is at the recordMarketFinalized function, when the market’s dispute window should already be over. At this point, no underflows should occur when calculating the reward. Additionally, the getCreationReward function is not affected by the unsafe arithmetic operation mentioned in the previous paragraph, as any known Augur universe is expected to have a lower creation time than the current block.timestamp.

Yet, to prevent unexpected behaviors when querying the getFinalizationReward function from off-chain clients (or other functions in future changes to the code base), consider reverting the transaction when an underflow occurs. Alternatively, depending on Augur’s use cases for this getter function, consider returning zero as the REP reward when the given market’s dispute window is not yet over.

Update: Fixed in PR #5564. The WarpSync contract now uses SafeMathUint256 in all calculations.

[M04] Lack of event emission

Several functions in the code base do not emit relevant events to log their execution. In particular:

Consider defining and emitting events whenever sensitive changes occur. This would allow effective notifications to off-chain clients about important modifications in the system.

Update: Partially fixed in PR #5577. The events have been added and logged except for the onTransferOwnership function of the AffiliateValidator contract, since the additional complexity would not be worth the gain.

Low severity

[L01] GnosisSafe contract does not fully match known implementation

Contracts in the GnosisSafe.sol file do not entirely match any of the known implementations available in Gnosis’s repository. Augur’s GnosisSafe contract claims to correspond to version 1.1.0, yet several differences with the official 1.1.0 version were detected. Moreover, while the GnosisSafe.sol file claims to have been verified in Etherscan on 2019-03-28, that is the date when the 1.0.0 version of the official Gnosis Safe Multisig wallet was submitted for verification (see code in Etherscan. The address was taken from Gnosis’s v1.0.0 zos.mainnet.json file).

Following we detail, for each contract in GnosisSafe.sol, which version matches (if any):

Note that the GnosisSafe.sol file was left out of the audit’s scope. We only checked for compliance with known implementations, but did not audit Augur’s implementation to verify functionality nor security. Consider either clearly documenting the above mentioned differences to raise awareness, or implementing a known non-modified version of the GnosisSafe wallet. Should Augur choose the latter, consider using the latest GnosisSafe version (1.1.1).

[L02] Transfer of REP tokens to the zero address

The transferRepBondOwnership function of the Market contract does not prevent the new owner from being the zero address.

In this situation, an initial report by the designated reporter or a fork migration could cause a transfer of REP tokens to the zero address, thus bypassing a restriction imposed by the ERC777 specification.

Consider restricting the REP bond owner to non-zero addresses.

Update: Fixed in commit 53a59728.

[L03] Warp Sync market finalization not recorded nor rewarded in migrated market

When the Warp Sync market is finalized, the recordMarketFinalized function of the WarpSync contract is called to reward the transaction sender and to update the data mapping with the latest values.

However, if the market is migrated to a child universe and then finalized, a consistency check in the notifyMarketFinalized function will inevitably fail, thus never calling recordMarketFinalized. As a result, the finalization of the market is not going to be recorded in the WarpSync contract and the user that initiated the finalization transaction will not be rewarded.

Consider handling the special case where a recurring market changes universe before it is finalized, or clearly documenting this behavior.

Update: Fixed in commit b95d70d7. The WarpSync market can no longer migrate.

[L04] Different callers can deploy proxies to same address

The deployProxyWithNonce function of the ProxyFactory contract does not use the msg.sender variable to compute the salt that is later passed to create2. As a consequence, two different callers can effectively deploy a proxy to the same address – potentially opening a race condition scenario should the saltNonce have insufficient entropy.

While this does not pose a security issue for Augur, consider either using msg.sender to calculate the salt or explicitly documenting this behavior in the docstrings of the deployProxyWithNonce function to avoid misuses. For an example of the former, refer to OpenZeppelin’s SDK ProxyFactory contract.

[L05] Proxy deployment using create2 does not revert upon failure

The deployProxyWithNonce function of the ProxyFactory contract does not revert the transaction when the create2 operation fails. Depending on the initializer payload, this may or may not cause the transaction to fail.

Following the “fail early and loudly” principle, consider reverting the transaction when the address returned by create2 is zero. For an example, refer to OpenZeppelin’s SDK ProxyFactory contract.

Update: Fixed in commit 7787dba1.

[L06] Erroneous docstrings in Proxy contract

The docstrings of the Proxy contract’s fallback function state that the function “forwards all transactions”. However, transactions with calldata equal to 0xa619486e00000000000000000000000000000000000000000000000000000000 (i.e., calling a masterCopy() getter) will not be forwarded to the masterCopy address.

Consider modifying the function’s docstrings to correctly describe its exact behavior.

[L07] Undocumented assembly blocks

The Proxy and ProxyFactory contracts include assembly blocks. See lines 25 to 37, 58 to 60, 88 to 90 and 104 to 106 in ProxyFactory.sol. Taking the first case as example, on top of the delegatecall-related logic found in typical proxy contracts, the assembly block intends to build a public getter for the first slot of the contract’s storage (i.e., the masterCopy internal state variable).

As this is a low-level language that is harder to parse by regular users, consider including extensive documentation regarding the rationale behind its use, clearly explaining what every single assembly instruction does. Clear documentation should make it easier for users to trust the code, for reviewers to verify it, and for developers to build on top of it or update it. As an example, refer to OpenZeppelin’s SDK Proxy contract.

Note that the use of assembly discards several important safety features of Solidity, which may render the code more error-prone. While no issues were identified in the audited version, consider implementing thorough tests to cover all potential use cases of these contracts to programmatically ensure they behave as expected, which should also help prevent introducing regression bugs in future modifications to the code base.

[L08] Ownership transfer fails silently

The transferOwnership function of the Ownable contract returns a boolean indicating if the transfer succeeds. However, it returns true in all cases, even when the _newOwner parameter is zero and the contract’s owner is not updated. Consider returning false in this scenario.

Update: Fixed in commit 550a6511.

[L09] Missing error messages in require statements

In the Affiliates contract, there is a require statement without an error message.

In the Market contract, most of the require statements do not include error messages. The only statements with error messages can be seen in lines 135 and 210.

Consider always including specific and informative error messages in all require statements.

[L10] Missing docstrings

The GnosisSafeRegistry, Affiliates, AffiliateValidator and WarpSync contracts completely lack documentation. This hinders reviewers’ understanding of the code’s intention, which is fundamental to assess not only security, but also correctness. Additionally, docstrings improve readability and ease maintenance. They should explicitly explain the purpose or intention of the functions, the scenarios under which they can fail, the roles allowed to call them, the values returned and the events emitted.

Consider thoroughly documenting all functions (and their parameters) that are part of the contracts’ public API. Functions implementing sensitive functionality, even if not public, should be clearly documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).

Update: Fixed in commit 6e2c2558. The documentation for these contracts has significantly improved.

[L11] Missing return value

The doInitialReport function of the WarpSync contract is supposed to return a boolean but does not explicitly return a value. Consider including a return statement in the function.

Update: Fixed in commit 8ca7638e.

Notes & Additional Information

[N01] Rogue operators in AffiliateValidator contract

The AffiliateValidator contract defines a mapping of operators. Operators are registered by the contract owner and are identified with their Ethereum address. The main purpose of an operator is to sign approved keys for users that go through a Know Your Customer (KYC) process, which will validate users in the affiliate system for any Augur market that specifies the corresponding AffiliateValidator contract.

Even though operators are expected to be fully trusted entities, their powers must be understood in depth to be aware of misbehaviors. For this reason, following we highlight three potentially unexpected actions that can be carried out by rogue operators.

  1. When a rogue operator sees a call to the addKey function in a node’s pool of pending transactions, they can front-run it with a new transaction using the same salt as the victim’s transaction. If the rogue operator effectively front-runs, then the salt will be registered as used and the second transaction using that same salt will fail. The rogue operator can do this indefinitely until being removed by the owner of the AffiliateValidator contract. This would prevent any other new key from being added, thus causing a Denial of Service on the targeted contract.
  2. A rogue operator may allow users to use the same key as their referrer. This would prevent the referrer from getting her corresponding affiliate fees, as the call to the validateReference function would be reverted.
  3. A rogue operator may allow users to clear their registered keys by approving and signing keys containing all zeros. This would also prevent the referrer from getting her affiliate fees, as the call to the validateReference function would be reverted.

Consider explicitly documenting the role of operators in Augur’s affiliate system, highlighting what is the set of expected actions that they can carry out in the protocol.

[N02] Affiliate keys may reveal user KYC information

The keys mapping of the AffiliateValidator contract stores a user-specific key (a bytes32 value), computed and signed by a trusted operator. The Augur team indicated that the key might be a hash of the user’s Know Your Customer (KYC) information, such as phone number and IP address. Different operators with the same information will produce the same user-specific key.

It should be noted that in this context the hash provides minimal confidentiality. This is because the number of possible inputs to produce the hash is likely small enough to be exhaustible. Since the key will be publicly available and associated directly with the user’s Ethereum address, an attacker can simply try all possible inputs until the correct hash is found, thereby revealing the KYC information. Typically, a large private random number is included when hashing low-entropy inputs to avoid this issue. Yet this would undermine the goal of having the same KYC information produce the same key.

The KYC procedure is out of scope for this audit, and revealing KYC information does not introduce a security vulnerability in the Augur protocol itself. Nevertheless, we highlight this issue as it may have not been considered and we understand the Augur team should be aware of the risk.

[N03] ECDSA parameter verification

The AffiliateValidator contract accepts ECDSA signatures over messages that prepend the appropriate magic constant. However, it does not restrict the possible value of s and v more than the native ecrecover operation. To avoid signature malleability, it is common practice to restrict s to the lower range and restrict v to either 27 or 28.

Note that the AffiliateValidator contract is not vulnerable to signature malleability (within a single contract instance) because the salt used in the signature must be unique. Nevertheless, to favor consistency, consider implementing the additional restrictions. Consider using OpenZeppelin’s ECDSA library as a reference implementation.

[N04] Unfulfillable condition in Affiliates contract

The getAndValidateReferrer function of the Affiliates contract intends to return the zero address when the _referrer address is zero, or when it matches the passed _account address. However, this last scenario is impossible to reach with a non-zero _referrer address. This stems from the fact that the setReferrer function (the only entry point to modify the referrals mapping) already ensures that the key and value cannot be the same.

To favor readability and avoid unnecessary validations, consider removing the unfulfillable condition.

[N05] Missing inheritance in Affiliates contract

To favor consistency and explicitness, consider modifying the Affiliates contract so that it inherits from the IAffiliates contract.

[N06] Redundant boolean check in ERC1155 contract

Lines 151 and 215 of the ERC1155 contract both explicitly compare a boolean value to true. This is a redundant operation because the result will be equivalent to the boolean value itself. Consider removing the redundant comparison.

[N07] ERC1155 token contract is an approved operator

The isApprovedForAll function of the ERC1155 contract returns true if the queried operator is the token contract itself. Although the contract can update balances at will, referring to it as an operator for a particular account may be confusing, particularly since it introduces an inconsistency between the isApprovedForAll function and the ApprovalForAll events. This behavior is also inconsistent with the ERC1155 reference implementation.

Consider returning false when the queried operator is the token contract itself.

[N08] Unnecessary ERC1155 hooks in internal burn functions

The _burn and _burnBatch functions of the ERC1155 contract both accept a doAcceptanceCheck flag that indicates whether they should call the onERC1155Received hook (or onERC1155BatchReceived) on the destination. However, the flag has no effect in these functions as the destination in both is the zero address.

To favor simplicity, consider removing the doAcceptanceCheck flag, and related logic, from the internal burn functions of the ERC1155 contract.

[N09] Redundant preconditions

The notifyMarketFinalized function of the WarpSync contract only allows creation of a new recurring market if the universe is not undergoing a fork. However, this condition is already checked during the call to the recordMarketFinalized function. Internally, recordMarketFinalized calls awardRep, which in turn calls the mintForWarpSync function of the ReputationToken contract. Finally, the execution reaches the updateForkValues function of the Universe contract. This function first validates that the current universe is not forking before continuing execution.

Similarly, the migrateThroughOneFork function of the Market contract ensures the market is not finalized, but this condition is checked in the following call to disavowCrowdsourcers

To favor simplicity and reduce gas costs, consider removing the redundant validations.

[N10] Inconsistent behavior in GnosisSafeRegistry contract

The GnosisSafeRegistry contract implements two different functions for registering new Gnosis Safe Multisig wallets: register and callRegister. However, by calling register instead of callRegister, it is possible to register wallets that:

  • May not have approved Cash tokens to the Augur contract
  • May not have approved Cash tokens nor ShareTokens to the CreateOrder contract
  • May not have approved Cash tokens nor ShareTokens to the FillOrder contract
  • May not have set a fingerprint in the Affiliates contract

Consider either providing a single function for registering wallets in the GnosisSafeRegistry contract, or explicitly documenting the described inconsistent behavior between the register and callRegister functions, highlighting under which circumstances each function is expected to be called.

[N11] Naming

[N12] Lack of explicit visibility in state variables

The following state variables are implicitly using the default visibility:

To favor readability, consider explicitly declaring the visibility of all state variables.

[N13] Named return variables

Named return variables are used inconsistently. For example, while the ProxyFactory contract names its return variables, the Affiliates contract does not. Consider removing all named return variables, explicitly declaring them as local variables, and adding the necessary return statements where appropriate. This should improve both explicitness and readability of the project.

[N14] Reuse zero address constant in Market contract

In line 344 of Market.sol, considering replacing address(0) with the NULL_ADDRESS constant to favor consistency.

[N15] Inconsistent imports

The following imports are not used:

Consider removing the unnecessary imports.

On the other hand, the following contracts rely on indirect imports:

Consider importing these contracts explicitly.

[N16] Typos

[N17] Declare uint as uint256

To favor explicitness, all instances of uint should be declared as uint256. See for example lines 280 and 322 of ERC1155.sol.

[N18] Not explicitly casting to address type

To increase the code’s readability, consider always explicitly casting all instances of this using address(this). Some examples of this issue can be found in lines 763, 766 and 785 of Market.sol.

[N19] Unused variable

The migrateThroughOneFork function of the Market contract defines a local variable _numOutcomes that is never used. Consider removing the definition.


1 critical and 4 high severity issues were found, all of them fixed and reviewed. Several recommendations were made to improve the project’s overall quality and reduce its attack surface.