Orderly engaged Guardian to review the security of its Omnichain contracts, supporting staking, vesting, and revenue claiming across multiple chains. From June 3rd to June 17th, a team of 6 auditors reviewed the source code in scope.
- Published
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Optimism, Base, Solana
- Sector
- Cross-chain
- 5 Critical
- 5 High
- 14 Medium
- 17 Low
- 0 Informational
Scope
-
gitlab.com/orderlynetwork/orderly-v2/omnichain-ledger
d68be172d9d5d0f7a764
Overview
Orderly engaged Guardian to review the security of its Omnichain contracts, supporting staking, vesting, and revenue claiming across multiple chains. From June 3rd to June 17th, a team of 6 auditors reviewed the source code in scope.
Issues Detected Throughout the engagement 10 High/Critical issues were uncovered and promptly remediated by the Orderly team. Several issues impacted the fundamental behavior of the protocol, following their remediation Guardian believes the protocol to uphold the functionality described for the Omnichain product.
Security Recommendation Given the number of High and Critical issues detected, Guardian supports an independent security review of the protocol at a finalized frozen commit. Furthermore, the Orderly team should drastically increase tests for cross-chain staking and revenue claims without the use of setters for key state variables. The engagement exposed multiple blind spots that should be thoroughly tested and presented numerous opportunities for system malfunction.
Findings 41
-
C-01 Critical Removed Request Is Always The Last Logical Error Resolved
Description
Proof of concept: PoC
Vesting._cancelVestingRequest() and Vesting._claimVestingRequest try to remove the current request by: 1. Overriding it in the storage with the last request in the array. 2. Deleting the last request.
It fails to execute the first step because it only updates the local variable to point to the last request, but no real storage update is done.
As result, the removed request is always the last one. A malicious user can use that to create a cancel request with large value followed by many 1-wei cancel requests. They can then cancel their large request many times because on each cancel, one of the 1-wei requests will be removed instead of the real one. This will cause the user's staked balance to grow indefinitely and result in stolen funds.
Recommendation
Do not assign values from storage to storage just before deleting them. Cache the value to move to memory, assign it to its new index, and then pop the last element from the storage.
Resolution
Orderly Team: The issue was resolved in commit 366de22.
-
C-02 Critical Tokens Stuck In Proxyledger When Claiming Vesting Validation Resolved
Description
Proof of concept: PoC
When claiming vested ORDER, the OmnichainLedgerV1 sends
PayloadType.ClaimVestingRequestBackwardmessage to the ProxyLedger. However, the payload validation in ProxyLedger does not implement theClaimVestingRequestBackwardpayload and instead reverts.This will result in tokens being stuck forever in the ProxyLedger because they will be minted to the ProxyLedger when the endpoint's
lzReceivegets executed and a compose message will be stored that will call the ProxyLedger. The said compose message will always revert and the user will lose their tokens.Recommendation
Change the
ProxyLedgerto support theClaimVestingRequestBackwardpayload.Resolution
Orderly Team: The issue was resolved in commit c643f97.
-
C-03 Critical Redeeming USDC Revenue Functionality Broken DoS Resolved
Description
In order to claim USDC revenue from ledger, the owner should mark the batch as claimed using
batchPreparedToClaim. This will also reduce thetotalValorAmountas much as the redeemed amount in the batch.The issue is that
totalValorAmountis always zero, causingfixedValorToUsdcRateScaledto be zero as well. Consequently, attempts to callbatchPreparedToClaimfail with theBatchValorToUsdcRateIsNotFixederror, making it impossible to mark the batch as claimed and preventing users from executing USDC claims.Recommendation
Increase
totalValorAmountbypendingValorwhen_updateValorVarsAndCollectUserValoris called.Resolution
Orderly Team: The issue was resolved in commit 17e40bf.
-
C-04 Critical Inability To Transmit Messages To Vault Chain Logical Error Resolved
Description
Proof of concept: PoC
LedgerOCCManagerrelays message requests from theOmnichainLedgerV1to the vault chain usingledgerSendToVault. This function will call theorderTokenOftcontract with the native fee and setLedgerOCCManageras the refund recipient.These relay requests will fail due to the fact that: 1. The
ProxyLedgerdoes not specify anymsg.valuesent to thelzComposefunction during execution. bytes memory options = OptionsBuilder.newOptions.addExecutorLzReceiveOption(_oftGas,0).addExecutorLzComposeOption(0, _dstGas, 0);- Owner can't fund
LedgerOCCManagerwith native currency, as there is no receive function. - If there are any excess fees, the refund transaction will revert.
Therefore,
LedgerOCCManagerhas no native currency to pay the message fees, preventing users to execute any function on ledger chain with a backward message.Recommendation
If the message fee is meant to be paid by Orderly, add a
receivefunction toLedgerOCCManagercontract to allow deposits and refunds to be processed and a privileged withdraw function. Otherwise, consider specifying theaddExecutorLzComposeOptionwith themsg.valuethat needs to be sent by the executor in order to pay for the backward message.Resolution
Orderly Team: The issue was resolved in commit a2f7330.
Guardian Team:
payloadType2BackwardFee[message.payloadType]was added but can lead to DoS when non-zero. Change the messaging fee toMessagingFee memory msgFee =MessagingFee(lzFee, 0); - Owner can't fund
-
C-05 Critical Stepwise Jump In User Pending Valor Logical Error Resolved
Description
User's pending valor is calculated based on the current accrued valor share, user's staked balance and claimed valor. The accrued valor share uses
valorPerSecondand elapsed time to calculate these shares.The issue relies on the admin being able to update this
valorPerSecondstate variable, using the permissionedsetValorPerSecond, without realizing the accumulated valor with the old rate. Any rate hike or decrease will directly affect user's claimable valor (positively or negatively).Recommendation
Consider moving the
setValorPerSecondfunction to theStaking.solcontract and callupdateValorVarsbefore the rate change.Resolution
Orderly Team: The issue was resolved in commit 2d44e9e.
-
H-01 High Valor Emitted Without Cap Logical Error Resolved
Description
The system uses two variables to calculate the valor emission,
totalValorEmittedandmaximumValorEmission, and performs a check to validate if the new emission and the total emitted will not surpass the max value.The issue is that
totalValorEmittedstate variable is never updated, so it's value is always 0. Therefore, the valor cap validations are non existent, and the system can emit valor indefinitely.Recommendation
Update the
totalValorEmittedstate variable, by adding thevalorEmissionin_getCurrentAccValorPreShareScaled:totalValorEmitted += valorEmissionResolution
Orderly Team: The issue was resolved in commit 17e40bf.
Guardian Team:
_doValorEmissionis a public function, and calling it directly without callingupdateValorVarswill result in broken internal accounting and inaccurate valor-to-share ratios since it will emit valor without updatingaccValorPerShareScaled. Change the visibility of the_doValorEmissionfunction to internal.Furthermore, with the introduced changes valor will be emitted when paused or when there are no stakers.
-
H-02 High Claim Rewards Callback Message Not Executable Logical Error Resolved
Description
Proof of concept: PoC
Users can claim
ORDERandesORDERrewards based on the merkle distributions. The issue arises when claimingesORDER, as the amount claimed will be staked, but will also try to send aClaimRewardBackwardpayload to the Vault chain.The internal
vaultRecvFromLedgerinProxyLedgercontract will fail as this callback performs the following check:require(message.token == LedgerToken.ORDER && message.tokenAmount > 0,"InvalidClaimRewardBackward");Therefore, every esORDER claim request will block the LayerZero pathway for the destination chain, as no new messages can be executed before clearing the failed one.
Recommendation
Avoid sending the message back to Vault Chain when claiming
esORDERrewards, by early returning after_stakeor creating an else statement.Resolution
Orderly Team: The issue was resolved in commit ec1350c.
-
H-03 High Partial Vesting Claim Requests DoS’ed DoS Resolved
Description
Partial vesting claim requests occur when users claim before the vesting linear periods. When these claims are executed ORDER tokens are sent to the user in vault chain, but unclaimed amounts are transferred directly to the
orderCollectorin the ledger chain.The issue is that
OmnichainLedgerV1will never have ORDER token balance, so thesafeTransferwill always fail as long as there is unclaimed amount. Therefore, users will need to wait for the full 90 days vesting period to be over as no partial claims are possible.Recommendation
Move the
safeTransfercall to theLedgerOCCManagerand create a function inOmnichainLedgerV1so that unclaimed amounts can be transferred to the collector.Resolution
Orderly Team: The issue was resolved in commit c643f97.
-
H-04 High Dust Amount DoS DoS Resolved
Description
Proof of concept: PoC
When the
OFTtoken receives a request to sendamountLDof tokens, it will deduct some dust amount from that. After that a slippage check is performed and ifamountLD - dustAmountis less than a givenminAmountLD, the transaction will revert.The problem found in
OCCManagerandLedgerOCCManageris that bothamountLDanddustAmountare set to the same value. This will result in inability to transfer tokens when there is a dust amount present, meaning no staking, withdrawing, claiming can be performed with these amounts.Ignoring the UX issues, this can be quite problematic for unstaking and vesting
esOrder. Imagine that a user has someesAmountthat they unstake and vest. After the vesting period ends, the user is not able to claim their order tokens becauseesAmounthas dust amount to be removed.Recommendation
A possible solution may be to pass the cleared
amountLDasminAmountLD. However, this can lead to some minor token losses when sending a message from the Ledger side to the Vault side. If you want to mitigate these, you can add an additional state variable that tracks dust amounts and adds them to the respective users' balances.Resolution
Orderly Team: The issue was resolved in commit bdc99c5.
-
H-05 High Missing whenNotPaused Modifier Causes Loss Of Funds Logical Error Resolved
Description
Proof of concept: PoC
Several contracts in the repository inherit Pausable from OpenZeppelin and correctly implement the
pauseandunpausefunctions. However, the modifierwhenNotPausedis not implemented in core contracts such as OrderOFT, OrderAdapter and ProxyLedger.The modifier should be added to user-facing functions to allow the owner to pause operations in the event of an emergency for example. A more critical issue also exists because OmnichainLedgerV1 correctly implements the
whenNotPausedmodifier while ProxyLedger does not.If the owner were to pause both contracts, users would still be able to call functions like
stakeon the ProxyLedger which would execute successfully but revert on destination chain. User's tokens would lose funds as their tokens would be burned on source chain, minted on destination chain but never staked on the user's behalf.Recommendation
Add modifier
whenNotPausedto core functions such assend,stake, andsendUserRequest.Resolution
Orderly Team: The issue was resolved in commit bb802a9.
-
M-01 Medium No Storage Gaps In Upgradable Contracts Upgradability Resolved
Description
The system uses upgradable contracts. The following parent contracts don’t use storage gaps, which will result in a corrupted storage if a variable is added/removed:
Omnichain:
- VaultOCCManager
- LedgerAccessControl
- OCCAdapterDataLayout and LzTestData
- ChainedEventIdCounter
- MerkleDistributor
- Valor
- Staking
- Revenue
- Vesting
Recommendation
Add storage gaps to the upgradable contracts.
Resolution
Orderly Team: The issue was resolved in commit 8dae135.
Guardian Team: Storage gaps and used storage slots should add up to 50 which is not the case for most of the current contracts.
-
M-02 Medium Missing Validations For Grants Validation Resolved
Description
grantonly validates if the array params match in length, but there are some crucial validations that should be enforced to correctly grant tokens to a holder:- Validate that the start and cliff times are in the future.
- Ensure that the cliff time is not before the start time.
- Ensure that the amounts being granted are greater than zero.
duration > 0cliffTime < startTime + duration
Additionally, when the owner creates a
regrant, it will add the new amount to the exiting grant, but it will overwrite the timestamps and durations. Setting a shorter duration can allow the user to claim all tokens immediately.Recommendation
Consider adding the validations above to correctly create a token grant.
Resolution
Orderly Team: Resolved.
-
M-03 Medium Signatures in Valor can be reused Logical Error Resolved
Description
The
TREASURE_UPDATER_ROLEcan callValor.dailyUsdcNetFeeRevenueto report for USDC revenue by using a signed message.However, there are no checks if the signature has already been used which allows for one signature to be reused unlimited times.
Recommendation
Add a nonce to the signature, hash it and check if it's been already used.
Resolution
Orderly Team: The issue was resolved in commit be65ca2.
Guardian Team:
data.timestampwas added to the signature in an attempt to fix M-03. However, signatures can still be reused as the same timestamp can just be passed. Validate whether the signature was used prior. -
M-04 Medium Vesting Claims DoS’ed With OFT Token Update DoS Resolved
Description
orderTokenOftis set in theOmnichainLedgerV1initialize function, but admin can usesetOrderTokenOftto update this token address at a later stage. There are a couple of issue that arise when performing this update:LedgerOCCManagerdoes not contain an admin function to update theorderTokenOftaddress. If
address is updated on
OmnichainLedgerV1and not inLedgerOCCManager, users will still be able to stake with the old token address.LedgerOCCManagerwill holdorderTokenOfttokens sent from vault chains. If the token address is
updated, any withdraw or claim will fail as the contract does not have balance of the new token.
- When users request vesting claims, the
unclaimedOrderAmountwill be sent to theorderCollector.
In case admin updates the
orderTokenOft, user claims will be DoS'ed as the contract may not have sufficient amount of the new token to transfer the collector.Recommendation
Avoid changing the
orderTokenOftaddress. Alternatively, addsetOrderTokenOftto theLedgerOCCManagercontract or always read the updated address from theOmnichainLedgerV1. Be aware that same amount of OFT tokens should me minted as the previous OFT token balance inLedgerOCCManagerto avoid issues with the stakes and vests.Resolution
Orderly Team: The issue was resolved in commit c643f97.
-
M-05 Medium Staking esOrder Without Balance Logical Error Resolved
Description
ProxyLedgerallows users to stake ORDER or esORDER. When theisEsOrderflag is set to true, users will effectively stake esORDER on theOmnichainLedgerV1contract, but will also be charged ORDER tokens, due to the fact thatvaultSendToLedgerwill deduct the staked amount from the user balance.Therefore, user will end up with an esORDER stake, and ORDER tokens will be sent to the
LedgerOCCManagercontract. Although user will be able to unstake, vest and re-claim the ORDER tokens, this is an unexpected behavior as esORDER staking should not be triggered directly.Additionally, if future upgrades give more reward weight to esORDER staking, users will be able to game the system by creating an esORDER stake with ORDER tokens.
Recommendation
Always use
LedgerToken.ORDERin the ProxyLedgerstake()and remove theisEsOrderparam.Resolution
Orderly Team: The issue was resolved in commit edebe01.
-
M-06 Medium Users May Receive Rewards For Only 1 Batch Logical Error Resolved
Description
The intended batch flow is as follows: 1. Users redeem valor in batch A. 2. Batch A finishes. 3. A trusted role marks the batch as claimable.
Whenever a user redeems their valor or claim USDC,
_collectUserRevenueForClaimableBatchis called to increase their withdrawable amount by adding the withdrawable amounts of the first claimable batch.If there are users that redeem their valor in between steps 2 and 3 from above (so
batch Ais finished andbatch Bis active) when they claim USDC when batch B becomes claimable they will be given the reward from only the first claimable batch even though they are entitled to rewards from two batches.Recommendation
Accumulate rewards for all claimable batches when claiming
Resolution
Orderly Team: The issue was resolved in commit d73c7ed.
-
M-07 Medium Incorrect Calculation Of USDC Required Per Batch Logical Error Resolved
Description
The
Revenuecontract tracks the amount of USDC tokens required per batch id, for users to redeem. It uses thefixedValorToUsdcRateScaledto calculate the USDC amount based on the batch valor amount.This USDC amount calculation is missing the
VALOR_TO_USDC_RATE_PRECISIONcorrection, so the value returned is greater than expect, thus invalid. Additionally, if the batch id is not claimable yet, thefixedValorToUsdcRateScaledvalue is not set (value 0), so all amounts will be 0 due to the multiplication.Recommendation
Add a division by
VALOR_TO_USDC_RATE_PRECISIONto fix the units.Resolution
Orderly Team: The issue was resolved in commit d73c7ed.
-
M-08 Medium Incorrect Access Control For setTotalUsdcInTreasure Access Control Resolved
Description
setTotalUsdcInTreasurewas meant to be called by theDEFAULT_ADMIN_ROLEand not theTREASURE_UPDATER_ROLE, as the latter will usedailyUsdcNetFeeRevenuewhich validates signatures and checks timestamps.Recommendation
Update the access control modifier in
setTotalUsdcInTreasureto useonlyRole(DEFAULT_ADMIN_ROLE)Resolution
Orderly Team: The issue was resolved in commit 6828c6a.
-
M-09 Medium Valor May Be Redeemed At An Undesirable Rate Access Control Acknowledged
Description
A user may observe that redeeming valor in
batch Ais quite profitable so they initiate aredeemValorrequest. However, due to their transaction taking quite a long time to be executed and delivered to the Orderly Network, their redeem request may now end up inbatch B. Inbatch Bthe redeeming terms may not be as good as the user desired.For example, a lot of valor accumulated without much profit or the admin called
Valor.setTotalUsdcInTreasureto decrease the usdc. The user will not be able to stop the transaction and in result, they will redeem at undesirable rate.Recommendation
Add an additional
batchIdparameter to theredeemValorfunction and revert the tx if it doesn't match the currentbatchId.Resolution
Orderly Team: Users will be warned about this behavior.
-
M-10 Medium Claiming Rewards Possible When Distributor Paused Logical Error Resolved
Description
According to the comments in MerkleDistributor: Contract is pausable by owner. It allows to pause claiming rewards.
However,
claimRewardsdoesn't have thewhenNotPausedmodifier. This is most likely becauseclaimRewardscalls functionupdateRootwhich implements it. However, the call toupdateRootis conditional - it happens only if there is an upgrade possible. Otherwise, claiming is still possible -even in paused state.Recommendation
Add the
whenNotPausedmodifier to theclaimRewardsfunction.Resolution
Orderly Team: The issue was resolved in commit 3be4fb4.
-
M-11 Medium USDC Claims Will Fail When Using OFT Wrappers Logical Error Acknowledged
Description
In order to make a batch claimable, admins need to update the
totalUsdcInTreasureusingdailyUsdcNetFeeRevenue. If the USDC amount added has 6 decimal precision (as most USDC tokens), then the user will receive a 6 decimal amount when theClaimUsdcRevenueBackwardpayload is executed.bool success = IERC20(usdcAddr).transfer(message.receiver, message.tokenAmount);The protocol plans to allow claims in multiple chains, using both native USDC or OFT wrappers. The issue is that OFT tokens have 18 decimals by default. Therefore, users will receive less tokens than expected when using OFT wrappers (If the protocol launches on BSC, the pegged USDC token has 18 decimals).
Additionally, as the ProxyLedger will transfer native USDC tokens, it should use
safeTransferinstead.Recommendation
Create the OFT token wrapper for USDC that will be used in the vault chains, overriding
decimalsto use 6 decimals instead of 18. Ensure that admin updates daily USDC revenue with correct decimals.Resolution
Orderly Team: Only Order token will be OFT wrapped.
-
M-12 Medium getRemainingBalance Should Round Up Logical Error Acknowledged
Description
Vestings are linearly unlocked after a cliff period during the release duration. Claimable balance of a vesting is calculated by subtracting the remaining balance from the total balance.
However, remaining balance of a vesting is calculated using the
mulDivformula fromMathLibrary and this formula rounds down by default. Rounding down the remaining balance means rounding up the claimable balance.Total claimable amounts will not be affected from this but intermediary claims will give users slightly more tokens than it should.
Recommendation
Roundings should be in favour of the protocol. Use the
mulDivfunction with selective rounding option from the same library.Resolution
Orderly Team: Acknowledged.
-
M-13 Medium Recall Can Be Frontrunned Gaming Acknowledged
Description
Owner has a right to
recalla grant and this function returns unclaimed tokens to the owner from the holder. Returning unclaimed tokens:- Creates unfair situations between holders. Let’s assume there are two different holders with exact
same cliff and release duration parameters but one of them claimed unlocked part of his vesting and the other didn’t claim anything yet. Recalling from both of these holders results in different user balances.
- Creates a surface for frontrunning attacks. Users can frontrun the
recalland claim their vestings’
claimableportion just before the recall.Recommendation
Consider adding a new functionality to recall only the remaining portion of a vest alongside
recall.Resolution
Orderly Team: Acknowledged.
-
M-14 Medium Lack Of Withdraw Functions Logical Error Acknowledged
Description
LedgerOCCManager, OCCManager (and potentially OmnichainLedgerV1) are expected to hold ORDER / USDC tokens which are burned or transferred when users claim.
The issue lies with the lack of a withdraw function for Owner to recover these tokens from the contract. Owner may wish to do this during a migration to a new contract. Without a withdraw function, these tokens may become permanently stuck in the contract.
Recommendation
Implement a withdraw function for the contracts which are expected to hold ERC-20 tokens.
Resolution
Orderly Team: Will consider add withdraw or do it during upgrades.
-
L-01 Low Implementation Contracts Can Be Initialized By Anyone Upgradability Resolved
Description
ProxyLedger, OmnichanLedgerV1 and LedgerOCCManager are all implementation contracts designed to be called through a proxy contract. However, the initialize function can be directly called by anyone.
This would allow a malicious actor to set storage variables such as
lzEndpointandowneron the implementation contract. While no direct risks were found, it is general good practice to preventinitializefrom being called.Recommendation
Use
disableInitializerin a constructor. See OpenZeppelin’s guide for more details.Resolution
Orderly Team: The issue was resolved in commit cdb53b6.
-
L-02 Low fixBatchValorToUsdcRate May Use Stale Rate Logical Error Resolved
Description
Users earn valor and collect USDC using their valors. The amount of USDC to collect is determined based on USDC to valor rate and this rate is fixed for every batch by the admin. However, fixing the rate for a batch is done a few days after the batch is finished (It is expected to be 2-3 days after), and the rate at that time is used.
Revenue earned during these days, which should be allocated for the current batch, are allocated for the previous batch. Ideally, the rate at the time when a batch is finished should be used for fairness in between batches. Also, there will be unfair distribution between batches if the fixing time is not exactly the same for every batch.
Recommendation
Consider using the rate exactly at the time when a batch is finished and also consider using smart contract automation systems like Chainlink Keeper.
Resolution
Orderly Team: The current sequence should avoid the problem.
-
L-03 Low Incorrect Error Message During WithdrawOrder Typo Resolved
Description
The error message in
vaultRecvFromLedgerforPayloadDataType.WithdrawOrderBackwardis "InvalidClaimRewardBackward". The contract also has error messages alluding theOrderlyBox, but this is not the correct contract and won't help debugging transactions.Recommendation
Correct the error message to "
InvalidWithdrawOrderBackward" and update the revert string contract.Resolution
Orderly Team: The issue was resolved in commit c643f97.
-
L-04 Low Use safeTransfer Instead Of transfer Optimization Resolved
Description
MerkleDistributorL1andOmnichainLedgerV1contracts usesafeTransferfor ERC20 transfers butProxyLedgerandOCCManagercontracts use regulartransfer.Recommendation
Consider using
safeTransferandsafeTransferFromin mentioned contracts too.Resolution
Orderly Team: The issue was resolved in commit 4cabfc4.
-
L-05 Low Misleading Comments Optimization Resolved
Description
Comments above the
buildOCCMessagefunction inProxyLedgercontract explainpayloadTypeparams. However, some of these comments are not correct and they are misleading. In the comment,RedeemValoris incorrectly matched with enum 6 while it should have been 9.ClaimUsdcRevenueis also not enum 7 but it is 10. Furthermore, remaining functions are not mentioned in the comments.Recommendation
Consider updating the Natspec comment.
Resolution
Orderly Team: The issue was resolved in commit 6b49009.
-
L-06 Low Debug Code In Production Optimization Acknowledged
Description
_msgPayloadand_optionsvariables inLedgerOCCManager:L114-115andOCCManager:104-105are not used in production but used only for testing purposes.Also, the commented line that has been used for testing in
LedgerOCCManager:179left in the production.Recommendation
Consider removing these lines from production code.
Resolution
Orderly Team: Acknowledged.
-
L-07 Low Unused Errors Optimization Resolved
Description
ValorPerSecondExceedsMaxValuecustom error inValorcontract,VestingPeriodIsOutOfRangeandDepositNotEnoughcustom errors inVestingcontract are defined but never used.Recommendation
Remove unused errors.
Resolution
Orderly Team: The issue was resolved in commit 45179fb.
-
L-08 Low Typo Typo Resolved
Description
There is a typo in L103 of the
MerkleDistributorL1contract. “But it there…” should be “But if there…”.Recommendation
Update the comment in the mentioned line.
Resolution
Orderly Team: The issue was resolved in commit 45179fb.
-
L-09 Low CEI is not followed in LockedTokenVault.claim() Reentrancy Resolved
Description
The claim function in the Vesting contract doesn't follow the CEI pattern - it first transfers tokens to the claimer and only after that updates their balance in the mapping.
If in the future this contract is used for other tokens with hooks functionality, an attacker can hijack the execution flow when claiming and drain the funds in the contract.
Recommendation
Follow the CEI pattern by first updating the
claimedBalancesand then transferring the tokens.Resolution
Orderly Team: Resolved.
-
L-10 Low Owner Can Revoke Ownership Logical Error Acknowledged
Description
All
Ownablecontracts allow the owner to renounce their ownership. This can leave the contracts in an unexpected state and hinder the functioning of the protocol.Recommendation
Consider utilizing
Ownable2Step.Resolution
Orderly Team: This operation will be carefully checked.
-
L-11 Low Owner Can Steal Undistributed Funds Logical Error Acknowledged
Description
MerkleDistributorL1.withdraw lets the admin withdraw the unclaimed funds after the distribution ends. However, an admin can at any time update the merkle root with endTimestamp = startTimestamp + 1 and immediately withdraw the funds.
Recommendation
Consider adding a required gap between startTimestamp and endTimestamp in proposeRoot.
Resolution
Orderly Team: Acknowledged.
-
L-12 Low USDC Revenue Claims Are Lost Logical Error Acknowledged
Description
Users will start a
claimUsdcRevenuetransaction from theProxyLedgerbut the contract might not have enough USDC balance. Therefore, users will effectively trigger claim on ledger chain, but transaction can't be completed on vault chain.Recommendation
Consider validating the claimed amount against the contract USDC balance before triggering the claim transaction.
Resolution
Orderly Team: Orderly is responsible to deposit enough usdc first.
-
L-13 Low Redundant Parameter In claimRewards() Logical Error Resolved
Description
ProxyLedger.claimReward accepts isEsOrder parameter and sets the token of the payload to either ORDER or esORDER. This is redundant because users claim different distributions which are already set for a given token.
Recommendation
Consider removing the
isEsOrderparameter and use theLedgerToken.PLACEHOLDERin the payload instead.Resolution
Orderly Team: The issue was resolved in commit 5b102ad.
-
L-14 Low Contracts Without Receive Can’t Use ProxyLedger DoS Acknowledged
Description
The OCCManager sets the msg.sender as the recipient for the LayerZero refunds when sending a tx from Vault -> Ledger. This means contracts with no receive function will not be able to send messages whenever there is a refund happening.
Recommendation
Either document that smart contracts interacting with the system must be able to receive funds, or let the user pass a
refundparameter and use it instead ofmsg.sender.Resolution
Orderly Team: Will inform our users.
-
L-15 Low Users Can’t Fully Claim Vestings Logical Error Resolved
Description
After the lock period and linear periods ends, users should be able to claim 100% of
esOrderAmount. Although, if the amount is an odd number, the total claimed will be 1 wei less, due to solidity rounding: return _vestingRequest.esOrderAmount / 2 + (_vestingRequest.esOrderAmount * vestedTime) /vestingLinearPeriod / 2;Recommendation
Consider returning
_vestingRequest.esOrderAmountwhenvestedTime > vestingLinearPeriod.Resolution
Orderly Team: The issue was resolved in commit 7141809.
-
L-16 Low Block Reorgs May Lead To Unexpected Errors Reorganization Acknowledged
Description
The protocol intends to use multiple chains as
vaultchains. One of this chains isPolygonknown for having many reorgs in the blockchain. These reorgs occur when an alternative version of the blockchain gains consensus, effectively rewriting a part of the blockchain’s transaction history.The following scenario may occur in a block reorg:
- claim rewards backward message is sent to Polygon
- mints ORDER tokens to ProxyLedger in one tx
lzComposetx sends ORDER tokens to the user. - A reorg may occur, discarding the ORDER mint tx,
but including the ORDER transfer tx. Users will receive double amount ORDER tokens when the
lzComposeis re-submittedRecommendation
Consider documenting the issue and monitoring as impact may be limited with LayerZero default configurations.
Resolution
Orderly Team: Will set larger block confirmations for polygon.
-
L-17 Low Message Fees Lost Logical Error Acknowledged
Description
A user may send a
claimRewardrequest and pays for all fees, but the merkle root undergoes update before their transaction is executed. Therefore, user will pay message fees but won't be able to claim the rewards. They will need to send a second transaction with the new merkle root in order to claim the rewards.Recommendation
Consider documenting the issue so the users are aware of this.
Resolution
Orderly Team: Will be added to documentation and FAQ.
No findings match.
Invariants 39
The review's fuzzing suite asserted 39 invariants. 32 held and 7 did not.
Every invariant tested
| ID | Invariant | Result |
|---|---|---|
OC-01 | User staked ORDER & esORDER should never exceed total staked amount | Held |
OC-02 | Total Valor Emitted Should Be Less Than Maximum Valor Emissions | Held |
OC-03 | User Vault Chain ORDER Balance Should Increase By Amount Claimed On | Held |
OC-04 | claimReward Sender Vault ORDER Balance Should Decrease By Amount When Staking | Held |
OC-05 | Increments of valor emissions should Be Less Than or Equal To Total Valor Emitted When Interacting with Ledger Chain | Broken |
OC-06 | Balance User Collected Valor Should Increase By Pending Valor When Interacting with | Held |
OC-07 | Ledger Chain Balance Pending Valor Should Have Been Reset To 0 When Interacting with Ledger Chain | Held |
OC-08 | Balance AccValorPerShareScaled Should Not Decrease When Interacting with Ledger | Held |
OC-09 | Chain Balance Total Staked Amount Should Increase By Amount When Increasing Ledger ORDER/esORDER Balance | Held |
OC-10 | Total Staked Amount Should Decrease When Decreasing Ledger ORDER/esORDER | Held |
OC-11 | Balance If isEsOrder is true User Staked Order Balance Should Stay The Same When | Held |
OC-12 | Staking If isEsOrder is false User Staked Order Balance Should Increase When Staking | Held |
OC-13 | User Staked Order Balance Should Decrease By Amount When Creating An Order Unstake Request | Held |
OC-14 | If isEsOrder is true User Staked esOrder Balance Should Increase When Staking | Held |
OC-15 | If isEsOrder is false User Staked esOrder Balance Should Stay The Same When | Held |
OC-16 | Staking Pending Order Balance Should Increase By Amount When Creating An Order Unstake | Held |
OC-17 | Request Unlock Timestamp After Should Equal block.timestamp + 7 days When Creating | Held |
OC-18 | An Order Unstake Request User Ledger Order Balance Should Increase By Pending Order Balance Before | Held |
OC-19 | When Canceling An Unstake Request Total Staked Amount Should Increase By Pending Order Balance Before When Canceling An Unstake Request | Held |
OC-20 | User Pending Order Balance Should Equal 0 When Canceling An Unstake | Held |
OC-21 | Request/Withdrawing Order User Unlock Timestamp After Should Equal 0 When Canceling An Unstake | Held |
OC-22 | Request/Withdrawing Order User Vault Order Balance Should Increase By Amount Received When Withdrawing | Broken |
OC-23 | Order or claiming a vesting request User Staked esOrder Balance Should Decrease By Amount when calling | Held |
OC-24 | esOrderUnstakeAndVest Vesting Request requestId Should Equal currentRequestIdBefore when calling | Held |
OC-25 | esOrderUnstakeAndVest Vesting Request esOrderAmount should equal amount requested when calling | Held |
OC-26 | esOrderUnstakeAndVest Vesting Request unlockTimestamp should equal block.timestamp + vestingLockPeriod when calling | Held |
OC-27 | esOrderUnstakeAndVest User currentRequestId Should Equal currentRequestIdBefore + 1 when calling | Held |
OC-28 | esOrderUnstakeAndVest User staked esOrder balance should increase by request esOrderAmount when | Held |
OC-29 | cancelling a vesting request Vesting request length for user should decrease by 1 when cancelling a vesting request | Held |
OC-30 | RequestId should have been deleted when cancelling or claiming a vesting request | Broken |
OC-31 | Vesting request length for user should equal 0 when cancelling all vesting | Held |
OC-32 | requests Order Collector Balance Should Increase By Unclaimed Order When claiming a partial | Broken |
OC-33 | vesting request User Collected Valor should Change by Pending Valor - Amount When redeeming | Held |
OC-34 | Valor Collected Valor should decrease by amount When redeeming Valor | Held |
OC-35 | Batch redeemedValorAmount Should Increase By Amount When redeeming | Held |
OC-36 | Valor Batch userChainedValorAmount Should Increase By Amount When redeeming | Held |
OC-37 | Valor ORDER token amount received on the Vault chain should be 0 when claiming Usdc | Broken |
OC-38 | Revenue User ChainedUsdcRevenue Should Be Reset when claiming Usdc Revenue | Broken |
OC-39 | User should have received USDC on the Vault chain When claiming Usdc Revenue | Broken |
More from Orderly
All 8 reports-
Solana Vault, Sol-CC and EVM Updates
53 findings1 critical · 6 high 53 findings: 1 critical, 6 high, 5 medium, 22 low, 19 informational -
Solana Vault
41 findings1 high 41 findings: 1 high, 3 medium, 18 low, 19 informational -
Strategy Vault Updates
30 findings1 high 30 findings: 1 high, 5 medium, 16 low, 8 informational -
Solana Staking
35 findings1 critical · 1 high 35 findings: 1 critical, 1 high, 4 medium, 29 low
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.
