As of 3 Nov 2026, LandX Finance has 1 audit report on Trustblock, by Quantstamp, dated 10 Nov 2022. That report lists no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| LandX Finance | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 5 |
| Medium | 0 | 1 | 3 |
| Low | 2 | 0 | 14 |
When `rewardsToDistribute()` is called, it mints the vested tokens and increments the `rewardSharesPerToken`. However, the `rewardSharesPerToken` is not incremented if the vesting has fully completed at the time of the function call, as seen in the following code segment on L245: ```=solidity if (elapsedDays >= rewardVestingDuration) { amountVested = MAX_REWARD_AMOUNT.sub(rewardVested.amountVested); _mint(address(this), amountVested); rewardVested.amountVested += amountVested; rewardVested.lastVestedAt = block.timestamp; } ```
Open the reportThe specified receiver of the `_deposit()` function in `xBasket` never receives the minted xBasket tokens as intended. Additionally, the `deposit()` and `mint()` functions check whether the receiver is able to mint/deposit the requested amount whereas the funds are taken from the `msg.sender`.
Open the reportThe `LNDX` contract uses the global variables `rewardSharesPerToken` and `feeSharesPerToken` to track the amount of rewards and fees to distribute on a per token basis. To track how many rewards each user should receive, the contract stores the amount the user is not entitled to in the mappings `rewardsPerGrant` and `feePerGrant`. The simplified computation for the rewards of a user is then `amountOfTokens * rewardSharesPerToken - rewardsPerGrant`. Whenever a user claims rewards, `rewardsPerGrant` will therefore need to be updated to exclude the rewards that have just been claimed by adding them to `rewardsPerGrant`. The code however subtracts the claimed rewards from `rewardsPerGrant` (L198), which means the user will be able to claim even more tokens with each claim. Similarly, the code subtracts the fee that has been distributed from `feePerGrant` instead of adding it (L192). Additionally, the amount used is likely wrong since all tokens - including unvested ones - are used when computing the rewards.
Open the reportThe `crop` state variable in `xToken` is set to the constant value, "CORN". The `xToken` constructor is designed to receive a value for the string of the crop being created, however, this is just used for the name of the ERC20 token, and not for the assignment of the `crop` state variable. This protocol is intended to work around multiple crops (e.g. xSOY, xWHEAT) however this constant state variable does not allow this as any reading of this state will read "CORN". Further, `xBasket` only considers four predetermined crops. While it is understood that these are the four crops that the `xBasket` will be beginning with, the team should have the functionality to add or remove crops used in the `xBasket` as needed.
Open the reportA common attack in DeFi is the sandwich attack. Upon observing a trade of asset X for asset Y, an attacker front-runs the victim trade by also buying asset Y, lets the victim execute the trade, and then executes another trade after the victim by trading back the amount gained in the first trade. Intuitively, one uses the knowledge that someone is going to buy an asset, and that this trade will increase its price, to make a profit. The attacker’s plan is to buy this asset cheaply, let the victim buy at an increased price, and then sell the received amount again at a higher price afterward. The `getShards()` function of `xToken` converts both the shards mint fee and the `xTokensAnnualRent` from `xToken` to USDC through a Uniswap router. The `amountOutMinimum` and `sqrtPriceLimitX96` variables specified in the `ExactInputSingleParams` required for the Uniswap swap are set to 1, and 0 respectively, which will execute the swap even if there is one USDC returned to the user or a price of zero for the `xToken`. A savvy user could front-run these swaps to minimize the USDC returned to the `xToken` contract following the swap and profit off of the price change. This issue is also present in `xBasket.autoCompoundRewards()`, except they would be front-running USDC to `xToken` swaps.
Open the reportThe `cToken` constructor instantiates the ERC20 symbol of all cTokens created as "cCorn". This creates cTokens with identical symbols.
Open the reportThe maximum allowable crop share value stored in `KeyProtocolVariables` is unused when minting a new LandXNFT. With nothing limiting the assignable value of the crop shares, LandXNFTs can be minted with crop share values that exceed the `maxAllowableCropShare` for the given crop as specified in `KeyProtocolVariables`.
Open the reportThe contracts use different precision values throughout the code base without properly documenting them. Constants such as `10 ** 3`, `1e6`, `10 ** 7` and `10**9` are just some of the constants that can be found in the code. Without documentation, it is impossible to verify that the correct precision is used everywhere.
Open the reportEIP-4626 specifies the following requirement for `totalAssets()`: > SHOULD include any compounding that occurs from yield. However, the current implementation of `totalAssets()` does not account for the potential yield.
Open the report`unstakePreview()` returns a tuple of the staked amount, rewards, and fees for a given `stakeID` if it were to be unstaked. However, the rewards value is not guaranteed to be accurate because `computeStakeReward()` is dependent on `rewardSharesPerToken`, which can be modified by the `rewardsToDistribute()` call in `unstake()`. The `previewUnstake()` function does not take into account possible changes to `rewardSharesPerToken` when `rewardsToDistribute()` is called.
Open the reportSimilar to the `xToken` sandwich attack issue, the `xBasket` also converts tokens using the UniswapV3 router without specifying a minimum amount out. Since sandwich attacks are only profitable for larger trades, it is unclear whether the yields from auto compounding are large enough to be at risk.
Open the reportWhile it may be acceptable to use hardcoded prices pre-launch, these prices should not be used following the LandX launch. If the Uniswap pool for the queried xToken does not exist, these hardcoded values are used.
Open the report`calculateGrantClaim()` currently behaves inconsistently. If the entire vesting duration has elapsed, it returns a tuple of the full vesting duration and any unclaimed rewards. If only part of the vesting duration has elapsed, it returns a tuple of the number of vested but unclaimed days, and the number of unclaimed rewards. An excerpt of the function is included below: ```=solidity // Check cliff was reached uint256 elapsedDays = block.timestamp.sub(grant.startTime - 1 days).div( 1 days ); // If over vesting duration, all tokens vested if (elapsedDays >= grant.vestingDuration) { uint256 remainingGrant = grant.amount.sub(grant.totalClaimed); return (grant.vestingDuration, remainingGrant); // <- Returns full vesting duration (of which only a subset may be unclaimed) } else { uint16 daysVested = uint16(elapsedDays.sub(grant.daysClaimed)); uint256 amountVestedPerDay = grant.amount.div( uint256(grant.vestingDuration) ); uint256 amountVested = uint256(daysVested.mul(amountVestedPerDay)); return (daysVested, amountVested); // <- returns only the unclaimed vested days } ```
Open the reportThe contracts currently use solidity version `0.8.6`. Due to a [medium severity bug](https://blog.soliditylang.org/2022/08/08/calldata-tuple-reencoding-head-overflow-bug/) in the solidity compiler since version `0.5.8`, this version is not in the [list of recommended versions](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity).
Open the reportThe `OraclePrices` contract inherits from both `Ownable` and `AccessControlEnumerable`. `Ownable` is unused and provides only a subset of the `AccessControlEnumerable` functionality and can thus be removed.
Open the reportIt is important to validate inputs, even if they only come from trusted addresses, to avoid human error. The functions `updateXTokenMintFee()`, `updateCTokensSellFee()`, `updatePayRentFee()`, `updateMaxAllowableCropShare()`, `updateHedgeFundAllocation()`, `updateSecurityDepositMonths()` in `KeyProtocolVariables` do not impose bounds on the values being set. This allows for fees over 100% to be assigned which would result in the loss of user funds. A non-exhaustive list of functions missing validation includes: * `KeyProtocolVariables`: Confirm that the following functions update variables to reasonable values (Ex. confirm that percentages cannot be above 100%). * `updateXTokenMintFee()` * `updateCTokenSellFee()` * `updatePayRentFee()` * `updateHedgeFundAllocation()` * `nft.setBaseURI()`: Validate that `newuri` is not an empty string. * `OraclePrices.setGrainPrice()`: Confirm that the grain exists. * `OraclePrices.setXTokenPrice()`: Confirm that the xToken exists. * `xToken.preview()`: Perform the same require statements performed in `getShards()`.
Open the reportThere is no error handling in the case that `oraclePrices.setGrainPrice()` fails in the Chainlink fulfill functions found in `OracleMulti`. Setting the grain price will fail if an invalid price was returned from the Chainlink request and the contract should react accordingly.
Open the reportIf the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed.
Open the reportThe function `grantLNDX()` checks that the `cliffInMonths` argument is less than five years when the require message states that it should be less than one year.
Open the reportEvery Solidity file specifies in the header a version number of the format `pragma solidity (^)0.8.*`. The caret (`^`) before the version number implies an unlocked pragma, meaning that the compiler will use the specified version _and above_, hence the term "unlocked".
Open the reportSolidity 0.8.x has a built-in mechanism for dealing with overflows and underflows. There is no need to use the `SafeMath` library (it only increases gas usage).
Open the reportStaking and unstaking xTokens, as well as auto-compounding xBasket rewards, does not emit events and is therefore untraceable. This inhibits off-chain monitoring of these events.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="landx-finance"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
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