Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · November 2025

ERC-20 Native and Token

for Aria

Guardian's review of ERC-20 Native and Token for Aria, published November 2025. The report records 33 findings across 2 review rounds, including 1 medium and 19 low.

Published
Review window
October 29 to November 5, 2025
Rounds
Main Review, Remediation Review
Language
Solidity
Chains
Story, BNB Chain
Sector
Real-world assets
  • 0 Critical
  • 0 High
  • 1 Medium
  • 19 Low
  • 13 Informational

29 resolved · 2 partially resolved · 2 acknowledged

Scope

7 files in scope · 323 nSLOC
FilenSLOCLines
contracts/claim/erc20-native/ERC20NativeClaim.sol2445
contracts/claim/erc20-native/vesting/BaseVesting.sol71141
contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol73142
contracts/claim/erc20-native/vesting/SnapshotVesting.sol3258
contracts/claim/erc20-native/shared/Claim.sol1423
contracts/claim/erc20-native/shared/ClaimAdmin.sol95142
contracts/claim/erc20-native/shared/MerkleWhitelist.sol1418

Findings 33

Main Review

26 findings · October 29 to 30, 2025
  1. M-01 Medium Token Configuration DoS DoS Resolved
    Location
    SnapshotVesting.sol
    Round
    Main Review

    Description

    The token defaults to address(0) (native). While token remains unset, anyone can call deposit() to fund the contract with ETH and, if whitelisted in the merkle tree and within the window, call claim() to withdraw ETH to themselves. Because setToken() is permanently disabled after totalClaimed > 0, a single successful ETH claim irreversibly locks the distribution to native token, preventing the admin from switching to the intended ERC20. An attacker can front-run by depositing ETH and claiming as soon as the window opens if the admin forgets to set the token first.

    Recommendation

    Require explicit token configuration before startTime or use a sentinel value instead of address(0).

  2. L-01 Low Invalid ClaimAdmin__ZeroAddress Revert Best Practices Resolved
    Location
    contracts/claim/erc20-native/vesting/BaseVesting.sol:70
    Round
    Main Review

    Description

    When token_ != token, the functions releasable and vestedAmount revert with ClaimAdmin__ZeroAddress(), which does not represent the true reason for the error.

    Recommendation

    Change the revert name to match the cause of the error.

  3. L-02 Low Underflow When Checking Releasable Warning Resolved
    Location
    contracts/claim/erc20-native/vesting/BaseVesting.sol:71
    Round
    Main Review

    Description

    Function releasable risks underflow if an admin were to change the cliff with setCliff such that vestedAmount(token_, uint64(block.timestamp)) becomes zero and totalClaimed is positive. This would block integrators from viewing the true amount of releasable funds.

    Recommendation

    Do not move the cliff after claims have been made.

  4. L-03 Low Fee-on-transfer And Rebase Tokens Not Supported Warning Resolved
    Location
    Global
    Round
    Main Review

    Description

    Fee-on-transfer and rebasing tokens are not currently supported by the system. For example, Claim._claim delivers the exact _amount. If the configured token is fee-on-transfer / rebasing / otherwise non-standard, the hook still credits claimed and totalClaimed with the full _amount even though the user receives less (or more).

    Recommendation

    Restrict tokens to plain ERC-20/native asset.

  5. L-04 Low Zero Claims Allowed Events Resolved
    Location
    ERC20NativeClaim.sol
    Round
    Main Review

    Description

    ERC20NativeClaim.claim allows a claim amount of zero, hence the event Claimed could be emitted repeatedly as _claimChecks only validates that if (claimed[msg.sender] != 0).

    Recommendation

    Either block zero claim amounts or clearly document this behavior.

  6. L-05 Low Missing whenNotPaused Modifiers Validation Acknowledged
    Location
    Global
    Round
    Main Review

    Description

    The deposit functions of the ClaimAdmin contract are callable by everyone but are not pausable.

    Recommendation

    Consider to add whenNotPaused to all external functions to follow best practices.

  7. L-06 Low Reliance On Contract Balance Risks Warning Acknowledged
    Location
    BaseVesting.sol
    Round
    Main Review

    Description

    If the admin withdraws tokens, the live balance shrinks. The recomputed totalReleased drops as well, and for users who already claimed based on the previous total allocation the new userTotalReleased can fall below their historical claimed amount, which makes _toClaim revert with BaseVesting__AllocationDecreased.

    Also note that with direct contract donations or whenever contract balance exceeds the total allocated, the contract immediately treats the higher balance as though it had been part of the original allocation. The linear curve _release therefore jumps up, so every user can instantly claim their proportional share of the donation instead of the donation vesting over the remaining duration. Users will also pull more than their recorded allocation, and total claims will exceed total allocation/total snapshotted.

    Recommendation

    Use a fixed ledger of “total allocated” tokens that is only ever updated by explicit funding events instead of reliance on .balance or balanceOf. Then _toClaim will not ignore the passed parameters.

  8. L-07 Low Start and End Time Movement DoS Claims DoS Partially resolved
    Location
    Global
    Round
    Main Review

    Description

    setStartTime and setEndTime remain callable by the admin after launch. Because every claim runs _checkStartTime() / _checkEndTime(), pushing startTime forward or pulling endTime back once users have begun claiming immediately bricks the flow for everyone else.

    There is no guard comparable to totalClaimed > 0, so a routine timetable adjustment moves the eligible window outside the current block timestamp and all claim calls revert

    Recommendation

    Consider preventing start and end time updates after claims begun.

  9. L-08 Low Cliff Release Percentage Change Leads To DoS DoS Resolved
    Location
    contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol:91
    Round
    Main Review

    Description

    The admin may lower cliffReleasePercentage at any time. After claims have started, the new curve in _release pushes the expected total release at the current timestamp below what has already been paid out. When _toClaim recomputes each user’s, userTotalReleased becomes smaller than claimed[msg.sender] so every subsequent claim reverts with BaseVesting__AllocationDecreased. This permanently DoSes all claimants.

    Recommendation

    Forbid percentage decreases after totalClaimed > 0

  10. L-09 Low Allocation Update Leads To DoS DoS Resolved
    Location
    contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol:68
    Round
    Main Review

    Description

    Updating allocations after any user has claimed can also DoS existing claimants. Function updateAllocations adjusts totalAllocated, which is later used as the denominator in _toClaim. If an admin onboards a new wallet or increases some other wallet once another user has already claimed, the recalculated share userTotalReleased = userAmount * totalReleased / totalAmount shrinks for the existing claimer, making it less than claimed[msg.sender] and triggering BaseVesting__AllocationDecreased on every future claim. In practice, a single late allocation can freeze all earlier participants.

    Furthermore, any allocation decreases after other users have already claimed risk an ERC20InsufficientBalance revert since old totalClaimed history is maintained even if the allocations have changed or the admin withdrew tokens.

    Recommendation

    Disallow allocation changes once totalClaimed > 0 or vesting begins.

  11. L-10 Low Updating Start Time Will DoS DoS Resolved
    Location
    contracts/claim/erc20-native/shared/ClaimAdmin.sol:97
    Round
    Main Review

    Description

    Raising startTime once vesting has begun makes _release recompute with a later start, decreasing totalReleased. The next claim hits the same AllocationDecreased revert, freezing everyone until the original start time is restored.

    Recommendation

    Prevent startTime from moving forward once vesting begins.

  12. L-11 Low Linear Duration Can Underflow DoS Resolved
    Location
    contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol:115
    Round
    Main Review

    Description

    If an admin shortens endTime below the current cliff, the subtraction in linearDuration underflows: endTime - cliff;

    Consequently, any _release function call reverts until the admin fixes the timestamps.

    Recommendation

    Modify setEndTime to ensure the new endTime doesn't fall below the current cliff.

  13. L-12 Low DoS On Merkle Root Change DoS Resolved
    Location
    contracts/iprwa/vault/admin/children/VaultWhitelistAdmin.sol:28
    Round
    Main Review

    Description

    Updating the Merkle root after anyone has claimed reruns _toClaim with the new (snapshot,total) pair. If the refreshed leaf lowers either value, userTotalReleased drops below claimed[msg.sender], so every subsequent claim reverts with BaseVesting__AllocationDecreased. This bricks all prior claimants even when the admin is just trying to admit new wallets or fix a typo in a snapshot amount.

    Recommendation

    Lock the root once totalClaimed > 0 or clearly document this risk.

  14. L-13 Low Native Claims To Smart Contract May Fail Warning Partially resolved
    Location
    Global
    Round
    Main Review

    Description

    Claims always transfer to msg.sender. If the whitelisted address is a smart contract without a payable receive/fallback, native token transfers will revert and the user cannot ever claim. There is no option to specify an alternative recipient address.

    Recommendation

    Consider adding a claim variant to allow a different recipient. Otherwise, clearly document this behavior.

  15. L-14 Low depositedRewards Unused Documentation Resolved
    Location
    ClaimAdmin.sol
    Round
    Main Review

    Description

    The depositedRewards which tracks deposits is never used and is not an accurate representation of how much funds are available for vesting. Also note that the passed _token does not have to match the token being vested out.

    Recommendation

    Clearly document this behavior.

  16. L-15 Low Total Allocated Not Used For Claim Calculation Warning Resolved
    Location
    Global
    Round
    Main Review

    Description

    The _toClaim function relies on balance + totalClaimed for calculation rather than totalAllocated. Consequently, users can pull more funds out than their intended allocation.

    Recommendation

    Clearly document this is intended behavior.

  17. L-16 Low Snapshot Leaves Must Be Consistent Warning Resolved
    Location
    SnapshotVesting.sol
    Round
    Main Review

    Description

    Each Merkle leaf encodes both the user’s snapshotAmount and a caller-supplied totalSnapshoted. During claim, the contract takes that same pair and computes the payout as snapshotAmount / totalSnapshoted. If whoever prepares the snapshot accidentally (or intentionally) gives one address a smaller totalSnapshoted than everyone else, the proof will still verify and that address will receive a disproportionately large share of the vested tokens.

    Recommendation

    Ensure all leaves have the same totalSnapshotted.

  18. I-01 Informational Unecessary Calculation Gas Optimization Resolved
    Location
    contracts/claim/erc20-native/vesting/BaseVesting.sol:114
    Round
    Main Review

    Description

    In function _vestingSchedule, when startTime equals the timestamp, _timePoints() returns -1 (requires calculation) but _release() simply calculates: (totalAllocation * 0) / duration() = 0

    This case could be shortcutted in _timePoints() .

    Recommendation

    If timestamp <= startTime return 0 timePoints.

  19. I-02 Informational Temporary Negative Allocation Reverts Warning Resolved
    Location
    contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol:81
    Round
    Main Review

    Description

    The updateAllocations() function validates that each user's allocation is non-negative immediately after each iteration, rather than after all updates are processed. Consequently, multi-step allocation adjustments where a user's allocation temporarily goes negative before being corrected to be positive will revert.

    Recommendation

    Clearly document this behavior.

  20. I-03 Informational Lack Of Zero Address Check On Admin Validation Resolved
    Location
    contracts/claim/erc20-native/shared/ClaimAdmin.sol:48
    Round
    Main Review

    Description

    The initializer does not validate that _admin is non-zero. If initialized with address(0), no one will hold DEFAULT_ADMIN_ROLE, which disables all core functionality.

    Recommendation

    Add a zero-address check for _admin and revert with ClaimAdmin__ZeroAddress() if _admin == address(0).

  21. I-04 Informational Unused Import Superfluous Code Resolved
    Location
    contracts/claim/erc20-native/vesting/SnapshotVesting.sol:4
    Round
    Main Review

    Description

    The IERC20 interface is imported in the SnapshotVesting.sol file but not used.

    Recommendation

    Consider to remove unused code.

  22. I-05 Informational Allocations Made For Zero Address Warning Resolved
    Location
    contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol:68
    Round
    Main Review

    Description

    Function updateAllocations accepts wallet = address(0) in the Allocation struct, which will strand a portion of totalAllocated.

    Recommendation

    Ensure allocations are appropriately passed by the DEFAULT_ADMIN_ROLE or add contract-level validation.

  23. I-06 Informational 100% Vesting At Cliff Not Possible Best Practices Resolved
    Location
    contracts/claim/erc20-native/vesting/CliffReleaseVesting.sol:122
    Round
    Main Review

    Description

    Function _checkCliffReleasePercentage forbids setting the percentage of the cliff allocation unlock to 100%. If the product requirement includes “full unlock at cliff", it will not be supported.

    Recommendation

    Consider if this is expected behavior and clearly document if so. ERC20NativeClaim can be used for 100% claims but a merkle root needs to be created.

  24. I-07 Informational Potential Overflow On Released Calculation Warning Resolved
    Location
    BaseVesting.sol
    Round
    Main Review

    Description

    The calculation uint256 userTotalReleased = (userAmount * totalReleased) / totalAmount; can potentially overflow when performing (userAmount * totalReleased) on large values before being able to reach the division.

    Recommendation

    Consider using mulDiv or bound allocation amounts appropriately.

  25. L-17 Low Grant/Revoke Role Always Emits Event Events Resolved
    Location
    AccessControlInternal.sol
    Round
    Main Review

    Description

    Function _grantRole ignores the boolean returned by EnumerableSet.add, so re‑granting a role that an account already holds still emits RoleGranted. Similarly, _revokeRole has the same issue on removal — a failed remove (account never had the role) still emits RoleRevoked. This may confuse off-chain indexers relying on events to reflect actual membership changes.

    Recommendation

    Clearly document this behavior.

  26. I-08 Informational Streamlining Storage Warning Resolved
    Location
    Global
    Round
    Main Review

    Description

    Both ERC7201 namespaced storage is used and plain storage is used, so care must be taken with upgrades.

    Recommendation

    Consider sticking with just ERC7201 namespaced storage.

Remediation Review

7 findings · November 5, 2025
  1. L-01 Low Invalid ClaimAdmin__ZeroAddress Revert Best Practices Resolved
    Location
    contracts/claim/erc20-native/vesting/BaseVesting.sol:137
    Round
    Remediation Review

    Description

    The function vestedAmount reverts when token_ != $.setup.token with ClaimAdmin__ZeroAddress(), which does not represent the true reason for the error.

    Recommendation

    Change the revert name to BaseVesting__TokenMismatch.

  2. L-02 Low Arbitrary Token Can Be Depositted Validation Resolved
    Location
    ClaimAdmin.sol
    Round
    Remediation Review

    Description

    As noted in , a _token that is not the token being vested can be deposited by users which will credit towards depositedRewards and emit a Deposited event. This may lead to issues where a user deposits the valid vesting token, the admin changes the vesting token, but users continue to deposit the old vesting token.

    Recommendation

    Consider restricting function deposit(address _token, uint256 _amount) so that the _token must match the $.setup.token.

  3. I-01 Informational startTime After cliff Is Possible Validation Resolved
    Location
    contracts/claim/erc20-native/vesting/BaseVesting.sol:84-87
    Round
    Remediation Review

    Description

    It is possible to set the startTime after the cliff with the setStartTime function which does not make sense.

    Recommendation

    Consider to revert in that case.

  4. I-02 Informational PausableUpgradable Not Used Best Practices Resolved
    Location
    ClaimAdmin.sol
    Round
    Remediation Review

    Description

    Currently the ClaimAdmin uses Pausable from solidstate instead of PausableUpgradeable from OZ which is inconsistent with the other inherited contracts: AccessControlEnumerableUpgradeable and UUPSUpgradeable.

    Recommendation

    Consider using OZ's PausableUpgradeable or clearly document this.

  5. I-03 Informational Small Amounts Rounds Down To 0 Warning Resolved
    Location
    BaseVesting.sol
    Round
    Remediation Review

    Description

    BaseVesting floors both the stream and per-user share. Consequently, tiny allocations therefore vest to zero and never cross the BaseVesting__ZeroReleased guard, leaving some users unable to claim and producing stranded dust.

    Recommendation

    Clearly document this behavior.

  6. I-04 Informational Redundant Check Best Practices Resolved
    Location
    MerkleWhitelist.sol
    Round
    Remediation Review

    Description

    Function setMerkleRoot validates that the _merkleRoot is not empty bytes with if (_merkleRoot == bytes32(0)) revert MerkleWhitelist__ZeroMerkleRoot();. However, _setMerkleRoot performs the exact same check: if (_merkleRoot == bytes32(0)) return;

    Recommendation

    Consider removing the redundant check.

  7. I-05 Informational Invalid Comment For Native Token Documentation Resolved
    Location
    ClaimAdminStorage.sol
    Round
    Remediation Review

    Description

    "The token to be claimed - address(0) for native token, otherwise ERC20." is invalid since the native token is represented by the NATIVE_SENTINEL_ADDR = 0x000000000000000000000000000000000000dEaD

    Recommendation

    Update the comment to appropriately reflect the native token.

More from Aria

  1. Contract Updates

    7 findings1 high 7 findings: 1 high, 3 medium, 1 low, 2 informational
  2. Token

    2 findings 2 findings: 1 low, 1 informational
  3. IP Real-World Assets

    27 findings4 high 27 findings: 4 high, 6 medium, 14 low, 3 informational

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote