Guardian's review of Push Chain Migration for Push Chain, published July 2025. The report records 19 findings across 2 review rounds, including 12 low and 7 informational.
- Published
- Review window
- June 20 to 30, 2025
- Rounds
- Main Review, Remediation Review
- Language
- Solidity, JavaScript
- Chains
- Ethereum
- Sector
- Infrastructure
- 0 Critical
- 0 High
- 0 Medium
- 12 Low
- 7 Informational
Scope
2 files in scope · 171 nSLOC
| File | nSLOC | Lines |
|---|---|---|
src/MigrationRelease.sol | 117 | 181 |
src/MigrationLocker.sol | 54 | 93 |
Findings 19
Main Review
17 findings · June 20 to 23, 2025-
L-01 Low Redundant Boolean Check Superfluous Code Resolved
Description
The
releaseVestedfunction verifies if user already claimed by checkingclaimedvested[leaf] = true. Asclaimedvestedis a boolean, there is no need to compare it to thetrueparam.Recommendation
Consider removing the comparison strict equality operator:
if (claimedvested[leaf] ) -
L-02 Low Unused State Variable Superfluous Code Resolved
Description
The
isClaimPausedstate variable is defined inMigrationRelease.solbut it is never used. Additionally, thewhenNotPausedmodifier already handles this feature.Recommendation
Remove
isClaimPausedstate variable. -
L-03 Low Redundant Leaf Derivation Gas Optimization Resolved
Description
bytes32 leaf = keccak256(abi.encodePacked(_recipient, _amount, _epoch));require(verifyAddress(_recipient, _amount, _epoch, _merkleProof) & instantClaimTime[leaf] = 0,"Not Whitelisted or already Claimed");The leaf is locally constructed using
_recipient,_amount, and_epoch, then passed toverifyAddress, which re-computes the same hash again. This is redundant and introduces unnecessary computation.Recommendation
Consider computing the
leafonce and passing it directly to the Merkle verification. -
L-04 Low Unnecessary Return Keyword Superfluous Code Resolved
Description
The explicit
returnstatement at the end of this conditional block is unnecessary, as the function execution will naturally return at this point.if (_token = address(0)) {transferFunds(_to, _amount);return;}Recommendation
Consider removing the explicit
return; -
L-05 Low Flawed Contract Detection Logic May Limit UX DoS Acknowledged
Description
The Push team intends to prevent smart contracts from being recipient by checking the code size using the following logic:
uint256 codeLength;assembly {codeLength := extcodesize(_recipient) // @audit What is this codeLength check supposed to prevent?}This check would block legitimate smart contract wallets (e.g., Gnosis Safe, ERC-4337 wallets) from interacting, which could limit usability.
Recommendation
Consider removing the
extcodesizecheck. -
L-06 Low Users May Lock Tokens In Wrong Contract Validation Acknowledged
Description
The
MigrationLockeris an upgradeable contract. Although some safety checks where enforced to avoid users initializing the implementation, nothing prevents a users from locking PUSH tokens in this contract by mistake. This is possible as thePUSHtoken is a constant and the contract is unpaused by default.Recommendation
Consider calling
_pause()in theconstructorso future calls to thelockfunction in the implementation contract reverts. -
L-07 Low Missing SafeERC20 Implementation Best Practices Resolved
Description
The
MigrationReleaseusesSafeERC20in the transfer function forrecoverFunds. However, theMigrationLockeralso contains arecoverFundsfunction but theSafeERC20lib is not used.Recommendation
Consider, using
SafeERC20library in theMigrationLockercontract. -
L-08 Low Missing New Epoch Event Events Resolved
Description
Owner will call
initiateNewEpochto start a new epoch for locking PUSH tokens. This is an important state change in the contract that should emit an event to notify off chain services.Recommendation
Consider emitting an epoch change event in the
initiateNewEpochfunction. -
L-09 Low Locks May Not Be Included In Merkle Root Warning Acknowledged
Description
The
fetchAndStoreEventswill fetch allLockedevents between two blocks. This script is meant to be used once per epoch, at the end of each epoch.If the locker contract is not paused before calling
initiateNewEpoch, the following edge case might appear:- epoch = 1, started at block 0
- Alice submits
locktx at block 100, Locked event uses epoch 1 - owner also submits
initiateNewEpochat block 100, epoch = 2 - team runs
fetchAndStoreEventsfiltering epoch 1 events. - startBlock = epochStartBlock(1) = 0
- endBlock = epochStartBlock(1 + 1) - 1= 100 - 1 = 99
locker.queryFilterwill not include Alice's lock event.
Recommendation
Consider checking blocks up to and including the next block epoch's start block in the
fetchAndStoreEventsscript. -
L-10 Low Validation For Leaf Sum Matching Best Practices Resolved
Description
Currently, there is no validation in
fetchAndStoreEventsto ensure that the sum of all leaves equals the actual amount locked on the contract for that epoch.Theoretically, issues with the RPC (e.g., null values due to rate limits or internal errors) could result in the script omitting some lock events during Merkle root construction.
Since this is a one-way migration for the Push token itself, it is critical to include post-execution validations to ensure consistency with the on-chain state.
Recommendation
Consider tracking the amount locked for each epoch on-chain. After constructing the Merkle tree off-chain, sum the leaves and compare the result with the on-chain locked amount to verify that the off-chain state has been correctly captured.
-
L-11 Low Missing Permit Functionality Informational Resolved
Description
The
lockfunctions enforces users to first approve their PUSH tokens to be spent my theMigrationLockercontract. However, the PUSH token has apermitfunctionality, which can't be used in the locker contract.Recommendation
Consider adding a function to lock tokens with permit signature.
-
I-01 Informational Typo In
releaseInstantFunction Best Practices ResolvedDescription
relaeseinstead ofreleaseRecommendation
Consider correcting it
-
I-02 Informational Release Time Not Emitted In Events Events Resolved
Description
The
MigrationReleasecontract emits theReleasedInstantandReleasedVestedwhen users claim their PUSH tokens. These events emit the recipient's address, amount released, and epoch.However, the natspec incorrectly states
emits a ReleasedVested event with the recipient address, amount, and release time, but the epoch value is emitted instead.Recommendation
Update the natspec to describe the correct parameters emitted in the release events, as the release time or
block.timestampwill be included in the events anyways. -
I-03 Informational Users May Double Claim Warning Acknowledged
Description
If a user locks a, claims a, and then locks b, the script currently tracks their total as a + b. This could allow them to claim twice in the same epoch.
Recommendation
Beware that there should be only one merkle root update per epoch.
-
I-04 Informational Locked Event Does Not Have Unique Identifiers Documentation Resolved
Description
The current natspec of the
lockfunctions states:Emits a Locked event with the recipient address, amount, and a unique identifierHowever, this was an old version of the contract, where an
idwas used in theLockedevent, instead of theepochvalue, which is not unique.Recommendation
Update the natspec to correctly describe the parameters emitted in the
LockedEvent. -
I-05 Informational Readme Addresses
setToggleLockFunction Informational ResolvedDescription
The
READMEfile key features states:Safety toggles to prevent/allow locking - Owner ControlledHowever, the
setToggleLockfunction was removed, and thelockis now based on thepause/unpausefunctionalityRecommendation
Make sure README clearly addresses the features contained in the smart contracts.
-
I-06 Informational Redundant Tree Construction In
proofArray.jsBest Practices AcknowledgedDescription
A tree is constructed for each user, and then again twice for both
getProofandverifyinproofArray.js.This is incredibly inefficient, as the tree remains the same for each user.
Recommendation
Consider constructing the tree once and passing it as an object instead.
Remediation Review
2 findings · June 30, 2025-
L-01 Low Unnecessary Indexed Event Params Events Acknowledged
Description
event NewEpoch(uint256 indexed epoch, uint256 indexed startBlock);This event will only be emitted once per epoch, which will likely be a different block number.
Recommendation
Remove the indexed event params.
-
I-01 Informational Offchain Balance Checks Affected By Donations Logical Error Acknowledged
Description
Push has added strict balance validations in the
fetchscript.However, if even 1 wei is accidentally or deliberately sent (donated) to the contract, these validations may cause the script to revert or throw an error.
Recommendation
Be aware of this edge case during debugging or triage.
If a
throwis observed, consider the possibility of donations as a potential cause.
No findings match.
More from Push Chain
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.
