Sandbox Sand Reward Pool

CertiKFiles named in the report

CertiK reviewed The Sandbox (Sandbox Sand Reward Pool) on 4 Feb 2022: 6 findings, 0 critical; 5 marked fixed, 1 acknowledged.

No open critical findings
Open the reportOpen The Sandbox’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings6

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High010
Medium001
Low004
MediumPossibility to bypass the `antiCompoundCheck`Fixed

The Sandbox has an `anti-compound` mechanism to "implement a time buffer for reward retrieval". The associated modifier is defined as follow: ```solidity modifier antiCompoundCheck(address account) { require( block.timestamp > antiCompound.lastWithdraw[account] + antiCompound.lockPeriodInSecs, "SandRewardPool: must wait" ); antiCompound.lastWithdraw[account] = block.timestamp; _; } ``` The issue is that the variable `antiCompound.lastWithdraw[account]` is not set when a user enters `stake()`, meaning a user does not have to respect the delay when he first enters the staking contract before 7 days. Below is an example of a scenario that would bypass the mechanism, with `antiCompound.lockPeriodInSecs : 10 080 (7 days)`. Exploit Scenario: 1. User enters the contract with `stake()`. \ Since `antiCompound.lastWithdraw[account]` is not defined, it is equal to 0. 2. After 2 days, user calls `exit()`, since `antiCompound.lastWithdraw[account]` is 0, the condition below is met, and the user can exit the staking contract. ```solidity require(block.timestamp > antiCompound.lockPeriodInSecs,"SandRewardPool: must wait"); ``` 3. The user sends his staking token to a new address, and go back to Step 1. \ The user needs to use a new address since his current address now has a correct value for `antiCompound.lastWithdraw[account]`.

LowAnti-compound system might cause an issueFixed

The Sandbox wants to ensure that people stake their tokens for a certain amount of time before being able to withdraw their rewards. Users can claim their rewards by : - Calling the `getReward()` function; - Exiting the staking feature with the `exit()` function. In both cases, the function to withdraw the rewards is `_withdrawRewards()` : ```solidity function _withdrawRewards(address account) internal antiCompoundCheck(account) { ``` This `_withdrawRewards()` function uses the `antiCompoundCheck` modifier: ```solidity modifier antiCompoundCheck(address account) { require( block.timestamp > antiCompound.lastWithdraw[account] + antiCompound.lockPeriodInSecs, "SandRewardPool: must wait" ); antiCompound.lastWithdraw[account] = block.timestamp; _; } ``` The modifier essentially ensures that a period of time is respected between 2 rewards withdrawals. The issue is that in case of major security issue regarding the project, users might need to exit the staking immediately with their rewards. \ However, if a user does not meet the previous condition from the modifier, the whole `exit()` transaction would revert and the user would be stuck. The auditors would like to know how The Sandbox team would deal this issue.

LowIncompatibility With Deflationary TokensFixed

The staking contract operates as the main entry for interaction with staking users. The staking users deposit tokens into the `SandRewardPool` contract and in return get a proportionate amount of shares. Later on, the staking users can withdraw their own assets from the pool. In this procedure, `_stake()` and `_unstake()` from `StakeTokenWrapper` are involved in transferring users’ assets into (or out of) the protocol. When transferring standard ERC20 deflationary tokens, the input amount may not be equal to the received amount due to the charged (and burned) transaction fee. As a result, this may not meet the assumption behind these low-level asset-transferring routines and will bring unexpected balance inconsistencies.

LowMissing Input ValidationFixed

The given input are missing the check for the non-zero address: - `SandRewardPool`.`setTrustedForwarder()`: Modify the `Trusted Forwarder`; - `SandRewardPool`.`recoverFunds()`: Send rewards tokens from the contract.

LowVariable Declare as ImmutableFixed

The variable `rewardPool` assigned in the constructor can declare with `Immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since will not be stored in storage. Still, values will be directly inserted the values into the runtime code.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (8)

  • contracts/src/solc_0.8/defi/SandRewardPool.sol
  • contracts/src/solc_0.8/defi/contributionCalculation/LandContributionCalculator.sol
  • contracts/src/solc_0.8/defi/interfaces/IContributionCalculator.sol
  • contracts/src/solc_0.8/defi/rewardCalculation/PeriodicRewardCalculator.sol
  • contracts/src/solc_0.8/defi/rewardCalculation/TwoPeriodsRewardCalculator.sol
  • contracts/src/solc_0.8/defi/StakeTokenWrapper.sol
  • contracts/src/solc_0.8/defi/interfaces/IRewardCalculator.sol
  • contracts/src/solc_0.8/defi/contributionCalculation/LandOwnerContributionCalculator.sol

Part of The Sandbox’s security record

The Sandbox
Audits8 audits by 2 firmsLast audit Aug 2023, by OpenZeppelin.
Critical findingsNo open critical findingsAcross the 8 reports, 1 critical finding was reported. It is marked fixed by the firm that reported it.
Open The Sandbox’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something 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