PlotDex Info
Invest in tokenized fractional real estate from $25. Power the world's first pricing standard for property. Build on the blockchain designed for real estate.
Team and KYC Verification
The KYC verification for this project is currently in progress.
The team has submitted their information and verification is pending.
TrustNet Score
The TrustNet Score evaluates crypto projects based on audit results, security, KYC verification, and social media presence. This score offers a quick, transparent view of a project's credibility, helping users make informed decisions in the Web3 space.
Real-Time Threat Detection
Real-time threat detection, powered by Cyvers.io,
is currently not
activated
for this project.
This advanced feature provides continuous monitoring and instant alerts to safeguard your assets from potential security threats. Real-time detection enhances your project's security by proactively identifying and mitigating risks.
For more information, click here.
Security Assessments
Summary and Final Words
No crucial issues found
The contract does not contain issues of high or medium criticality. This means that no known vulnerabilities were found in the source code.
Contract owner cannot mint
It is not possible to mint new tokens.
Contract owner cannot blacklist addresses.
It is not possible to lock user funds by blacklisting addresses.
Contract owner cannot set high fees
The fees, if applicable, can be a maximum of 25% or lower. The contract can therefore not be locked. Please take a look in the comment section for more details.
Token transfer can be locked
Owner can lock user funds with owner functions.
Token cannot be burned
There is no burning within the contract without any allowances
Ownership is not renounced
The owner retains significant control, which could potentially be used to modify key contract parameters.
Contract is not upgradeable
The contract does not use proxy patterns or other mechanisms to allow future upgrades. Its behavior is locked in its current state.
Scope of Work
This audit encompasses the evaluation of the files listed below, each verified with a SHA-1 Hash. The team referenced above has provided the necessary files for assessment.
The auditing process consists of the following systematic steps:
- Specification Review: Analyze the provided specifications, source code, and instructions to fully understand the smart contract's size, scope, and functionality.
- Manual Code Examination: Conduct a thorough line-by-line review of the source code to identify potential vulnerabilities and areas for improvement.
- Specification Alignment: Ensure that the code accurately implements the provided specifications and intended functionalities.
- Test Coverage Assessment: Evaluate the extent and effectiveness of test cases in covering the codebase, identifying any gaps in testing.
- Symbolic Execution: Analyze the smart contract to determine how various inputs affect execution paths, identifying potential edge cases and vulnerabilities.
- Best Practices Evaluation: Assess the smart contracts against established industry and academic best practices to enhance efficiency, maintainability, and security.
- Actionable Recommendations: Provide detailed, specific, and actionable steps to secure and optimize the smart contracts.
A file with a different Hash has been intentionally or otherwise modified after the security review. A different Hash may indicate a changed condition or potential vulnerability that was not within the scope of this review.
Final Words
The following provides a concise summary of the audit report, accompanied by insightful comments from the auditor. This overview captures the key findings and observations, offering valuable context and clarity.
Smart Contract Analysis Statement
Contract Analysis
The VestingVault contract implements a structured vesting and distribution layer for the PLOD presale. Buyers receive a five-percent unlock at TGE and then linear vesting of the remaining ninety-five percent over 365 days after a 90-day cliff. Advisors use a fixed default schedule of a 365-day cliff followed by a 1095-day linear vest. Team beneficiaries use a fully configurable cliff, vesting duration, and start offset. The overall design is clean and well-isolated: the buyer pool and the beneficiary pool are funded from separate flows, so administrative beneficiary additions can never encroach on the buyer allocation. The contract reads each buyer's allocation directly from PresaleCore via an immutable reference, which removes any data-duplication risk. The areas that need attention are mostly operational:
- The vault assumes the owner has deposited at least TOKEN_CAP plus the running total of beneficiary allocations before TGE is set. The deposit happens off-chain and the contract does not verify it. An under-funded vault leads to early claimers receiving their tokens and late claimers seeing their claims revert when the balance is exhausted. The internal accounting handles this gracefully (each individual late-claim transaction rolls back cleanly with no state corruption), but the underlying funding gap is invisible until it bites. A single guard in setTGETimestamp that requires the vault's PLOD balance to cover all known obligations would close this.
- Setting the TGE timestamp requires the presale to have ended on the PresaleCore side. The only ways the sale ends are the owner calling endSale, the owner advancing past the last stage manually, or the last stage selling out with auto-advance enabled. If demand falls short of the full cap and the owner is unresponsive, the sale can remain in the started-but-not-ended state indefinitely, which keeps the TGE gate closed and blocks every buyer, advisor, and team claim forever even though the buyers have already paid the treasury. Pairing the existing gate with an automatic or permissionless end-of-sale fallback (or relaxing the gate after a sufficient deadline) closes this deadlock.
- Once a TGE timestamp is set but not yet reached, the owner can re-set it to any other future timestamp within a 365-day window, as many times as they want. While the previously-set TGE has not been reached, every buyer, advisor, and team claim reverts. The TGE value locks forever once block.timestamp catches up to it, so the post-TGE behaviour is safe, but the pre-TGE window is fully at admin discretion. A cap on the number of resets or on the delta between successive resets would bound the cumulative delay.
- The vault uses single-step Ownable transfer like the rest of the project. Because renounceOwnership is deliberately disabled, a mistyped transferOwnership cannot be recovered from except by the unintended new owner cooperating. Switching to Ownable2Step removes this risk by requiring the receiver to call acceptOwnership.
Ownership Privileges
The ownership of the contract has been retained by the deployer and is intended to be a multisig. The owner retains full privileges including:
- Setting and resetting the TGE timestamp until it has been reached, within a 365-day forward window.
- Pausing and unpausing all claim functions (buyer, advisor, and team).
- Adding, updating, and removing advisors and team beneficiaries, but only while TGE has not been set. Once TGE is set, the beneficiary set is frozen.
- Rescuing arbitrary ERC-20 tokens accidentally sent to the contract, except for PLOD itself which is blocked on that path.
- Rescuing excess PLOD above outstanding obligations, gated by both saleEnded on PresaleCore and TGE being set.
- No ability to mint PLOD: the vault only releases what was deposited into it.
- No ability to touch the buyer pool to satisfy beneficiary obligations: the beneficiary path is funded from a separate on-add transferFrom from the owner.
- RenounceOwnership is deliberately disabled, so admin cannot be lost accidentally during vesting.
Security Features
The contract implements several positive security features:
- Buyer-pool and beneficiary-pool isolation. Beneficiary allocations are pulled from the owner via safeTransferFrom at add and at upward update, and refunded on downward update or pre-TGE removal. The buyer pool is never deducted to satisfy a beneficiary obligation.
- Strict pre-TGE-only window for adding, updating, and removing beneficiaries, which means the beneficiary set is frozen at the moment the TGE timer locks. Buyers know exactly what they are vesting against from that moment forward.
- The rescueExcessPLOD function refuses to drain below outstanding obligations, computed as buyer tokens sold plus active beneficiary allocations minus everything claimed so far. It is also gated behind both saleEnded and TGE being set, so it cannot fire while either side is still in motion.
- The PLOD token reference and the PresaleCore reference are both immutable, so they cannot be repointed mid-vesting at malicious targets. The TGE value, once reached, also locks forever.
- Checks-effects-interactions ordering on every claim path, with nonReentrant on every external entry point that moves tokens.
- A renounceOwnership override that reverts, preventing accidental loss of admin during the vesting period.
Note - This audit report consists of a security analysis of the VestingVault smart contract. This analysis did not include economic analysis of the contract's tokenomics. Moreover, we only audited the main contract for the PlotDex team. Other contracts associated with the project (the token and the presale) were audited in parallel but reported separately, and the broader PlotDex ecosystem was not part of this engagement. We recommend investors do their own research before investing.
Files and details
Findings and Audit result
medium Issues | 3 findings
Pending
#1 medium Issue
Vault has no on-chain check that it holds enough PLOD to honour all claims
The vault assumes the owner has pre-deposited at least TOKEN_CAP plus the running totalBeneficiaryAllocated in PLOD before TGE. The deposit is an off-chain transfer that the contract does not verify. If the owner under-funds or forgets to fund the vault, early claimers drain whatever is there and late claimers see safeTransfer revert. State changes happen before the transfer in the claim functions so each individual late-claim transaction reverts cleanly with no permanent state corruption, but the user still cannot receive what they paid for and there is no on-chain remediation path - the owner has to top up off-chain. The rescueExcessPLOD path correctly refuses to drain below outstanding obligations, so it cannot make this worse, but the underlying under-funding remains an operational risk that the contract does not catch.
Pending
#2 medium Issue
Owner can defer TGE indefinitely while it has not yet been reached
setTGETimestamp accepts any timestamp in the range from block.timestamp plus one second up to block.timestamp plus 365 days and can be called as many times as the owner wishes until block.timestamp reaches the currently-set TGE value. The owner can therefore keep moving the TGE forward by up to 365 days at a time, with no cap on the total number of resets. While the previously-set TGE has not been reached, all buyer, advisor and team claims revert with BeforeTGE. Buyer funds are already at the treasury at this point, so the buyers have paid but cannot claim. This is a deliberate centralization that the owner has full control over until TGE is reached; the contract is internally consistent, the issue is the breadth of admin discretion.
Pending
#3 medium Issue
Owner can permanently block all claims by never finalizing the presale
setTGETimestamp requires presaleCore.saleEnded to be true. The saleEnded flag only becomes true through three paths: the owner explicitly calls endSale, the owner manually calls advanceStage from the last stage, or the last stage actually sells out while autoAdvance is enabled. If demand falls short of the 280.5M-token, 49.14M USD cap (entirely plausible for any pre-TGE presale) and the owner is unresponsive or hostile, none of those paths fire. The sale stays in the saleStarted-but-not-saleEnded state forever, TGE can never be set, and every buyer, advisor, and team claim reverts permanently. Buyer funds have already moved to the treasury before this point, so the buyers have paid but cannot ever claim. This pairs with the TGE-deferral concern - the owner has two independent mechanisms to indefinitely block claims and the contract guards neither.
low Issues | 2 findings
Pending
#1 low Issue
Vesting pause has no maximum duration
VestingVault inherits OpenZeppelin's Pausable and exposes onlyOwner pause and unpause entry points that gate claim, claimAdvisor and claimTeam. There is no maximum pause duration. After TGE, a paused vault blocks every claim path indefinitely. Buyers have already paid the treasury at this point, so the impact is direct: paid users cannot receive the PLOD they purchased while the pause is in effect.
Pending
#2 low Issue
Single-step ownership transfer on the vesting admin
VestingVault uses the single-step OpenZeppelin Ownable. If the current owner calls transferOwnership with a mistyped or wrong address, the vault's admin moves to that address in one transaction. The contract correctly disables renounceOwnership, so there is no way to clear ownership and reset; recovery requires the unintended new owner to cooperate.
optimization Issues | 5 findings
Pending
#1 optimization Issue
Replace storage compound assignment with read-modify-write through a stack-cached local
Several state variables are updated with the compound assignment operator. For storage variables the equivalent x = x + y form using a stack-cached local saves a small amount of gas because the compiler can generate a single load and a single store instead of issuing the compound update. Affected updates are in the claim and beneficiary-management code paths.
Pending
#2 optimization Issue
Loop in _removeBeneficiary can use unchecked increment and cache the array length
_removeBeneficiary scans the list to find the target and uses swap-and-pop. The counter increment can be made unchecked because the index is bounded by list.length, and list.length is read on every iteration of the condition test. Caching it once and using unchecked increment is a clean micro-optimisation.
Pending
#3 optimization Issue
Constructor can be payable
A non-payable constructor has an implicit msg.value == 0 check inserted by the compiler. Marking the constructor payable removes that check and saves a small amount of deployment gas. There is no functional effect at runtime.
Pending
#4 optimization Issue
Boolean and-and conditions can be nested if statements
Several short-circuit conditions inside view and admin functions use the boolean and-and operator. Splitting them into nested ifs produces marginally smaller bytecode and clearer fail-fast structure. Affected views and the TGE-set guard are the candidates.
Pending
#5 optimization Issue
Beneficiary removal could be O(1) instead of O(n) via index mapping
_removeBeneficiary linearly scans the list to find the beneficiary's index before performing the swap-and-pop. With MAX_BENEFICIARIES set to 200 the worst case is bounded, but maintaining a mapping from address to index alongside the list would make removal O(1). This is only worth doing if the team expects high churn; for a presale with a fixed small set of advisors and team members the current code is acceptable.
informational Issues | 5 findings
Pending
#1 informational Issue
Removed beneficiaries leave stale data in their storage struct
_removeBeneficiary sets exists to false and totalAllocation to zero but leaves claimed, cliffSeconds, vestingDurationSeconds, startOffsetFromTge and kind set to their previous values. Because the contract gates every claim on exists, a removed beneficiary cannot claim. If the same address is re-added pre-TGE, the addAdvisor or addTeamBeneficiary else branch reassigns the full struct so the stale fields are overwritten. There is no exploitable bug; the only cost is slightly wasted storage and a tiny readability issue when someone inspects raw storage.
Pending
#2 informational Issue
Custom error NotABeneficiary is declared in the interface but never thrown
Two custom errors declared in IVestingVault are not thrown anywhere in the implementation. TGEAlreadySet is intentionally kept for ABI backward compatibility per its NatSpec note. NotABeneficiary looks like a leftover declaration from an earlier draft. Removing it tidies the interface without breaking anything.
Pending
#3 informational Issue
Naming convention TGE is kept capitalised in function and event names
Functions and events such as setTGETimestamp and TGETimestampSet keep the TGE abbreviation capitalised. The Solidity style guide prefers strict mixedCase. The project is consistent across both contracts so this is purely cosmetic.
Pending
#4 informational Issue
Buyer allocation is read from PresaleCore at every claim
claim reads the buyer's total allocation from PresaleCore via an external view call every time. Once the sale has ended, the value cannot change. Caching the allocation in the vault at first claim would save the external call cost on subsequent claims at the cost of one extra storage write the first time. This is purely a gas optimisation - the current design is simpler and correct.
Pending
#5 informational Issue
renounceOwnership is intentionally disabled and reverts
VestingVault overrides renounceOwnership to revert. This is a deliberate and recommended choice for a vesting contract because losing the admin mid-vesting would prevent any future emergency response. We are noting it for completeness so it is documented as design intent rather than an oversight.