Orderly engaged Guardian to perform a security review of their new feature which enables users to stake assets cross-chain from Solana. From the 24th of February to the 5th of March, a team of 6 auditors reviewed the source code in scope.
- Published
- Review window
- February 24 to March 5, 2025
- Language
- Rust, Solidity
- Chains
- Solana
- Sector
- Staking
- 1 Critical
- 1 High
- 4 Medium
- 29 Low
- 0 Informational
Scope
-
gitlab.com/orderlynetwork/orderly-v2/omnichain-ledger
cbd3cfbdb222e18ea585be89754dcbd032893643 - gitlab.com/orderlynetwork/orderly-v2/solana-proxy
Overview
Orderly engaged Guardian to perform a security review of their new feature which enables users to stake assets cross-chain from Solana. From the 24th of February to the 5th of March, a team of 6 auditors reviewed the source code in scope.
Findings 35
-
C-01 Critical Rewards Can Be Claimed Through send_request.rs Validation Resolved
Description
When claiming rewards from Solana, the
send_claimprogram is expected to be used where the user first submits a proof, then theSendClaimfunction encodes the user’s rewards and merkle root into the payload.However, an attacker may bypass
send_claimand use thesend_requestprogram instead to claim rewards.SendRequestdoes not validate that the payload type is notClaimRewardSolana. The attacker is therefore able to set their desired claim amount and easily obtain a merkle root to match the current active root of the desired distribution.On the Ledger chain,
MerkleDistributorhandles reward claims from Solana differently, without requiring a merkle proof, as it expects the check to be performed on Solana. However, by bypassingsend_claimthe attacker is able to claim any arbitrary amount from the distribution, draining theLedgerOCCManagerof all ORDER tokens.Recommendation
In
send_request: 58validate that the payload type is notClaimRewardSolana.Resolution
Orderly Team: The issue was resolved in commit cd0eaa8.
-
H-01 High Misconfigured OApp Config Results In Failed Messages Configuration Acknowledged
Description
Block Confirmation Mismatch
In
layerzero.config.ts, messages sent from Solana to Orderly require 10 block confirmations. However, on the Orderly chain, the receive configuration requires 32 block confirmations (as seen in theReceiveUln302contract: Explorer Link, using thegetUlnConfig functionwith OApp address 0x68835941c7C300bFEF44D1D68b83798791901eB8 and remoteId 30168).This mismatch causes the sending OApp (Solana) to proceed with only 10 confirmations, while the receiving OApp (Orderly) rejects any message with fewer than 32 confirmations. As a result, messages will be blocked until either:
- The sending OApp increases the outbound block confirmation requirement to 32, or
- The receiving OApp decreases the inbound confirmation threshold to 10.
NOTE: Increasing the outbound block confirmation will also mitigate against any re-org risk.
DVN Mismatch
Similarly,
layerzero.config.tssets the required DVN to aLayerZeroLabs address (4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb). However, on the Orderly chain, the required DVN points to a dead DVN (0x690b1857EaA8c55850547d7C22148C0B99a71dCd). This discrepancy means that:- The sending OApp (Solana) pays the LayerZero Labs DVN to verify packets,
- But the receiving OApp (Orderly) relies on a dead DVN, which performs no verification.
As a result, messages will be blocked until the receiving OApp updates its configuration to remove or replace the dead DVN.
Recommendation
- Ensure that block confirmation settings match on both the sending and receiving OApps.
- Update the DVN configuration to ensure that both the sending and receiving OApps reference the same,
active DVN.
Resolution
Orderly Team: Acknowledged. 11
-
M-01 Medium ClaimReward Backward Fee Is Always Paid Unexpected Behavior Acknowledged
Description
When users claim rewards both from EVM chains and Solana, they pay backward fee to cover the message fees from Orderly network back to theirs. However, some
distributionIdsare rewarding onlyesORDER.When these tokens are claimed, they are being staked in the system and no backwards message is sent. In result, users who claim their
esORDERrewards pay an unfair fee.Recommendation
You can track which
distributionIdis rewardingesORDERon the vault sides and don't charge fee if the user specifieddistributionIdmatches it.Resolution
Orderly Team: Acknowledged.
-
M-02 Medium ledgerOappSend Fails Due To Fees Not Sent Logical Error Resolved
Description
With the
ILedgerOapp(ledgerOappAddr).ledgerOappSend(occMessage);call from theOCCManager, ETH value is not sent to theLedgerOApp.Because native value is not forwarded, and Orderly does not subsidize cross-chain messages,
_lzSendwill revert due to lack of messaging fee provided (errorNotEnoughNative) and all messages back to Solana through the OApp will be permanently DoS'd.Recommendation
Forward native value to the
LedgerOAppso that theledgerOappSendfunction call can succeed. This is achieved by:- Making the function
ledgerOappSend payable - In
LedgerOCCManager, calculate and forward the native fee.
Resolution
Orderly Team: The issue was resolved in commit 05fe8af.
- Making the function
-
M-03 Medium Receiver_token_account May Not Be Initialized DoS Resolved
Description
Proof of concept: PoC
lz_receiveis called by the executor after a user sends a request with theClaimUsdcRevenuepayload type to the Ledger.In the
LzReceivestruct, it is expected that thereceiver_token_accountis already initialized by the user. However, there is a possibility that this account is not initialized. If that happens, the executor's call tolz_receivewill always revert with error “AccountNotInitialized”, causing a DOS and loss of fees.Recommendation
Initialize the account if it is not already initialized with
init_if_needed.Resolution
Orderly Team: The issue was resolved in commit 28077ba.
-
M-04 Medium Destination Gas For Payload Types Not Set Logical Error Acknowledged
Description
When sending a message from Solana to Orderly, the anticipated gas cost for executing
lzReceivemust be specified. Currently, this is determined byget_enforced_options, which provides a single gas value based onSENDorSEND_AND_CALL.However, in
LedgerOApp,lzReceiveprocesses various payload types, each requiring different gas amounts. Using a single gas value results in users either: a) overpaying for simple operations, or b) underpaying, causing transactions to fail due to insufficient gas.Recommendation
Consider implementing a mapping of payload type to destination gas in the Solana proxy, similar to the existing
payloadType2DstGasmapping inOCCManager.sol.Resolution
Orderly Team: Acknowledged.
-
L-01 Low Common Options When Sending Messages To EVM Chains And Solana Unexpected Behavior Resolved
Description
According to the LZ docs, when sending messages to Solana, the specified
addExecutorLzReceiveOptionparameters arecompute_unitsand lamports.Currently,
LedgerOCCManager.buildOCCLedgerMsg()will use the sameaddExecutorLzReceiveOption(_oftGas, 0). This may result in unexpected behavior because the gas andcompute_unitsare not the same.In addition, you should send at least 1500000 lamports as mentioned in the docs.
Recommendation
Consider passing different values when sending to Solana.
Resolution
Orderly Team: The issue was resolved in commit 0401f8e.
-
L-02 Low Incorrect AdminRoleTransferred Event Informational Resolved
Description
In
TransferAdmin:applythe event emits the newly set admin as theold_admin, because it usesctx.accounts.proxy_config.adminafter is has already been updated. Consequently, theAdminRoleTransferredemits inaccurate information.Recommendation
Cache the old admin and use that cached value in the event instead.
Resolution
Orderly Team: The issue was resolved in commit 5915655.
-
L-03 Low Stakes From Solana May Not Have Empty Payload Best Practices Resolved
Description
When users stake their
ORDERfrom Solana, they can pass arbitrary data in the compose message. Each of these fields are validated exceptchainedEventIdandpayload. ThechainedEventIdparameter is not used if the messages comes from Solana, so that's okay.The payload however is further passed to
OmnichainLedgerV1.ledgerRecvFromVault(). Even though it's not used there, for additional safety it's better to not use it.Recommendation
Use empty bytes for the payload.
Resolution
Orderly Team: The issue was resolved in commit 4c9f0c8.
-
L-05 Low Transfer_admin() Does Not Follow Two-Step Ownership Best Practices Acknowledged
Description
In the current implementation, the admin is the single point of failure in the
apply()function inside of thetransfer_admin.rsinstruction due to the single-step transfer.Recommendation
Set pending admin first and then the pending admin has to accept the ownership
Resolution
Orderly Team: Acknowledged.
-
L-06 Low Unused Constraint In send_request.rs Warning Resolved
Description
In
send_request.rs, the constraintbackward_fee.order_backward_fee < msg_fee.native_fee@ProxyError:InsufficientMessagingFeeis commented out and remains unused. This is inconsistent with thesend_claim.rsinstruction, where the constraint is actively enforced.Currently, no overflow risk is present due to the
overflow-checks = truesetting inCargo.toml. However, if this setting were to change in the future, the absence of this constraint could introduce an overflow risk.Recommendation
Consider uncommenting and enforcing the constraint in
send_request.rsto maintain consistency withsend_claim.rsand mitigate potential overflow risks if overflow checks are ever disabled in future configurations.Resolution
Orderly Team: The issue was resolved in commit c6a597a.
-
L-07 Low Precision Loss When Sending To Solana Informational Acknowledged
Description
EVM
$ORDERhas 18 decimals precision, but the token on Solana has 10 decimals. This means users will suffer small losses on token bridging.Recommendation
Document this so the users are aware.
Resolution
Orderly Team: Acknowledged.
-
L-08 Low Backward Fee May Not Match payloadType2LzOptions Warning Acknowledged
Description
When sending a message from Solana to Orderly, if ORDER tokens are expected in the backward message, an
order_backward_feeis charged. However, this fee is applied uniformly across all payload types, without accounting for potential differences in gas requirements.On the Orderly chain, the
LedgerOAppcontract maps payload types to gas options, meaning that different payload types may require varying amounts of gas.This mismatch can lead to inconsistencies between the backward fee charged on Solana and the actual gas sent back from Orderly to Solana.
Recommendation
Consider implementing a mapping of payload types to backward fees on Solana instead of applying a single
order_backward_feeacross all payload types. Otherwise, document this behavior.Resolution
Orderly Team: Acknowledged.
-
L-09 Low Users Will Be Charged Backwards Fee Even If Their Request Is A No-Op Informational Acknowledged
Description
When users initiate withdrawal requests or they claim their vestings or rewards, the amount they are eligible to may be 0. In this case, a cross-chain message won't be executed, but they will still have paid to it in a form of a backwards fee.
Recommendation
No code changes needed, but document this so the users are careful.
Resolution
Orderly Team: Acknowledged.
-
L-10 Low orderAmountForCollect Should Round Up Rounding Acknowledged
Description
In the function
_unstakeOrderNow, the penalty for early unstaking is calculated as:orderAmountForCollect = (_amount * UNSTAKE_NOW_COLLECT_PERCENT) / 100; // 5% of withdrawn amountHowever, this calculation rounds down due to Solidity's integer division behavior, which benefits the user instead of the protocol.
Recommendation
To ensure the penalty always rounds up in favor of the protocol, modify the calculation using ceiling division:
orderAmountForCollect = (_amount * UNSTAKE_NOW_COLLECT_PERCENT + 99) / 100;Resolution
Orderly Team: Acknowledged.
-
L-11 Low Proofs Can Be Submitted In A Paused State Validation Resolved
Description
The
SubmitProof:apply()function insend_claim.rsdoesn't check if the proxy is currently paused or not. This allows users to submit proofs during a paused state and can result in unexpected behaviors.Recommendation
Consider disabling proof submissions if the proxy is paused.
Resolution
Orderly Team: The issue was resolved in commit 205027f.
-
L-12 Low Solana Chained ID Ahead By One Warning Resolved
Description
For EVM chains, the first message sent cross-chain has a
chainedEventIdof 0. In contrast, the first cross-chain message sent from SOL has asolanaChainEventIdof 1 due to the pre-increment:srcEid= solanaEid + solanaChainEventId.This difference may be unexpected for Orderly's off-chain systems.
Recommendation
Be aware of this difference and adjust the SOL
eventIdas necessary for off-chain systems to function appropriately.Resolution
Orderly Team: The issue was resolved in commit 3f4524b.
-
L-13 Low Unordered Execution May Increase unlockTimestamp Unexpected Behavior Acknowledged
Description
Transactions sent via the Solana proxy to the Orderly network may not execute in the same order they were submitted. This unordered execution can lead to unexpected results.
For example, consider the following scenario:
A user intends to first execute a
withdrawOrdertransaction, since theunlockTimestamphas been reached, and subsequently initiate a newCreateOrderUnstakeRequest.The user submits the
withdrawOrderrequest viasend_requeston the Solana proxy, immediately followed by aCreateOrderUnstakeRequest.However, due to the non-sequential execution of transactions on the Orderly network, the
CreateOrderUnstakeRequestmight execute before thewithdrawOrder.If this happens, the
unlockTimestampis extended by an additionalunstakeLockPeriod, causing the subsequentwithdrawOrdertransaction to fail.When the
withdrawOrdertransaction fails due to the extended lock period, the fees previously paid for its submission through the Solana proxy are forfeited.Recommendation
Clearly document this behavior.
Resolution
Orderly Team: Acknowledged.
-
L-14 Low OApp Delegate Should Be Multisig Warning Acknowledged
Description
The
proxy_config's delegate has critical controls over the OApp's configuration. To maximize safety, it is recommended for the delegate to be a multi-sig.Recommendation
Consider using a multi-sig for the OApp's delegate.
Resolution
Orderly Team: Acknowledged.
-
L-15 Low Wrong Quote When Paying With Lz Token Unexpected Behavior Resolved
Description
When sending crosschain requests through the solana-proxy, users specify the maximum fee they are ready to pay in both native tokens and
lz token.pub native_fee: u64, pub lz_token_fee: u64However,
quote_claim.rsandquote_send.rshardcodepay_in_lz_tokentofalsewhen it quotes the endpoint. Because of this, the quoting mechanism won't work forlz tokenpayments even though the solana proxy allows such messages.Recommendation
Allow the user to specify whether they want to pay with
lz token.Resolution
Orderly Team: The issue was resolved in commit 784be09.
-
L-16 Low Redundant Message payloadType Check Superfluous Code Resolved
Description
The require statement would never revert as the check is already performed in the if condition in the function
ledgerSendToVault().Recommendation
Consider removing the require statement.
Resolution
Orderly Team: The issue was resolved in commit 53f8eea.
-
L-17 Low compose_msg Not Verified Validation Acknowledged
Description
For staking from Solana, the OFT is transferred with the composed message carrying the
OCCVaultMessageto be processed on the Ledger Chain.The compose message is intended to be created by Orderly's frontend, however any user can specify a compose message and custom front ends could be built.
This opens the user to potential issues such as if the composed message contents are malformed and reverts upon
lzComposeexecution, the user would have lost their funds since the OFT was burnt on src chain.Recommendation
Clearly document these risks to users.
Resolution
Orderly Team: Acknowledged.
-
L-18 Low Solana Restart Related Risks Unexpected Behavior Acknowledged
Description
The internal state in the
solana-proxyandOFTStorewould be reverted to previous state in case if the Solana chain restarts.Recommendation
Implement an check to verify the
LastRestartSlotstate to detect outdated configuration.Resolution
Orderly Team: Acknowledged.
-
L-19 Low CancelAllVestingRequests Can Still Be Submitted Best Practices Partially resolved
Description
The
CancelAllVestingRequestpayload is no longer supported, but it was left in the payload enum, so the payload types won't be shifted and the system will remain backwards compatible.However, the code still allows sending such request from the vault sides and the users who initiate them will pay fees for a 100% reverting transaction.
Recommendation
Consider not allowing
CancelAllVestingRequeststo be sent.Resolution
Orderly Team: The issue was resolved in commit 4136571.
-
L-20 Low Rate Limiter Vulnerable To Resource Exhaustion Unexpected Behavior Acknowledged
Description
The rate limiter's implementation utilizes a basic token bucket model without transaction weighting, making it vulnerable to large transaction dominance.
An attacker can submit a small number of resource-intensive transactions, consuming the rate limit and preventing other users from submitting transactions, leading to potential DoS and resource starvation.
Recommendation
To prevent large transaction dominance, implement a tiered token bucket system. Create separate buckets for distinct transaction size ranges (e.g., small, medium, large).
This ensures dedicated capacity for all transaction sizes, preventing large transactions from monopolizing resources and ensuring fairness.
Resolution
Orderly Team: Acknowledged.
-
L-21 Low Transfer Function Used Warning Resolved
Description
In the
LedgerOCCManager'swithdrawTofunction, the owner can withdraw ether to a specified address. This is currently done using a low-level transfer function.However,
transfer()only forwards 2300 gas, which is insufficient for the recipient to execute any non-trivial logic in areceive()or fallback function, such as when recipient is a multisig wallet.Recommendation
Consider using the low-level call function instead of the transfer function.
Resolution
Orderly Team: The issue was resolved in commit be89754.
-
L-22 Low LedgerOApp Can Receive Messages In Paused State Validation Resolved
Description
The
LedgerOAppcan be paused by calling thepause()function. However,_lzReceive()doesn't use thewhenNotPausedmodifier which makes it possible for the proxy to receive messages while it's paused.In most cases, the proxy on the Solana chain should be paused as well, but in case the protocol team wants to pause only receiving messages on Orderly Network, it won't work.
In addition, even if both proxies are paused, any messages sent before the pause, but still not received, can be executed.
Recommendation
Be aware of this behavior and consider implementing the
whenNotPausedmodifier onlzReceive().Resolution
Orderly Team: Resolved.
-
L-23 Low Missing Storage Gap In LzTestData Warning Acknowledged
Description
The storage layout for
LedgerOCCManageris:LzTestData(no gap)LedgerAccessControl(5 gap slots)OCCAdapterDatalayout(50 gap slots)LedgerOCCManager(45 gap slots)
LzTestDatadoes not have a storage gap, so caution should be taken not to introduce variables for future upgrades which would lead to collision issues.Recommendation
Do not introduce new variables to
LzTestDatafor future upgrades.Resolution
Orderly Team: Acknowledged.
-
L-24 Low Insecure Proxy Initialization Validation Acknowledged
Description
The current implementation of the proxy initialization functionality is susceptible to front-running attacks as there is no any checks for the signer address.
It has to be ensured that there is no possibility for malicious users to front-run the critical functionality of the protocol. As there is no sufficient validation, the function can be called by any account.
Recommendation
Consider adding the
ProgramDataaccount and check for the signer to be the expectedupgrade_authorityfrom theprogram_data.Resolution
Orderly Team: Acknowledged.
-
L-25 Low Redundant Mappings Warning Resolved
Description
The
LedgerOAppcontract on the Ledger chain has its ownchainId2Eidandeid2ChainIdmappings. If the mappings are updated, the update is not reflected in the mappings within of theLedgerOCCManagercontract.This may lead to inconsistent endpoint<>chain id's across the two contracts. Furthermore, the
chainId2Eidis not even utilized beyond the setter functionsetChainId2Eid.Recommendation
Consider having the
LedgerOAppquery theLedgerOCCManager's eid <> chain id mappings to have one source of truth.Resolution
Orderly Team: The issue was resolved in commit f1108aa.
-
L-26 Low Pause On Lz_receive Lead To Stuck Tokens DoS Acknowledged
Description
Currently, Pause is used on
lz_receivein both the Solana proxy and Solana OFT. Suppose a user sends order tokens from the Ledger to Solana. When the user initiates the call on the Ledger the send is not paused; however, when the Executor callslz_receiveon Solana OFT, it is paused.This means that the user does not receive the order tokens on both chains, causing them to become stuck in the middle. It also causes every Executor call to revert.
Recommendation
Ensure a retry system is implemented and documented.
Resolution
Orderly Team: Acknowledged.
-
L-27 Low USDC Amount Is Not Converted To Local Decimals Decimals Acknowledged
Description
When users claim their
USDCrevenue, they will be creditedusdcRevenueAmounton the source chain. This amount is in the same decimal precision for all chains (most likely 6 decimals).However, there are some
LayerZerosupported networks, like BNB, for example, where theUSDCtoken has different decimals (on BNB it's 18). This will cause a severe loss of funds for these users.Recommendation
Convert the amount of
USDCreceived to its local decimals amount when it's received inProxyLedger.vaultRecvFromLedger()Resolution
Orderly Team: Acknowledged.
-
L-28 Low Rate Limiter Can Be Updated But Is Not Implemented Informational Acknowledged
Description
The
set_peer_config.rsfile includes functionality to update the rate limiter. However, the rate limiter is not implemented in any of the Solana proxy instructions, making this update function redundant.Recommendation
Review whether the
update_rate_limiterfunction is necessary.Resolution
Orderly Team: Acknowledged.
-
L-29 Low ledgerOappSend Fails When Options Unset Logical Error Resolved
Description
In the
ledgerOappSendfunction, gas for the destination chain (Solana) is retrieved from thepayloadType2LzOptionsmapping and appended to options. However, ifpayloadType2LzOptionsis unset, it returns a value of zero for gas.This results in a stuck message on Solana, as no gas is paid for the executor to process the transaction. This behavior differs from the design in the
LedgerOCCManagercontract, which handles EVM flows differently.In the
buildOCCLedgerMsgfunction, if_dstGas = 0, a default value of 200000 is assigned to prevent execution failures (see LedgerOCCManager.sol#L130.Furthermore, the likelihood of
payloadType2LzOptionsbeing unset is high because:- The live
LedgerOCCManagercontract on the Orderly chain haspayloadType2DstGasunset for all
payload types.
- No test files or scripts indicate that the protocol intends to set
payloadType2LzOptions.
Recommendation
Modify the
ledgerOappSendfunction to assign a default gas value iftypeOptions.gas = 0Resolution
Orderly Team: The issue was resolved in commit 46dbcf5.
- The live
-
L-30 Low Contracts Lack Withdraw Functions Logical Error Partially resolved
Description
The
LedgerOAppcontract includes areceivefunction, which allows it to accept native ETH. However, it lacks a withdrawal function, meaning any excess ETH that accumulates cannot be recovered when the contract is no longer in use. This could lead to stranded funds.Other related contracts, such as
ProxyLedgerandLedgerOCCManager, already include awithdrawTofunction, suggesting thatLedgerOAppshould have one for consistency. Similarly, theproxy_token_accountis designed to hold protocol-owned USDC, which users claim.However, there is no function to withdraw surplus USDC, potentially leaving funds locked in the contract. Since
LedgerOCCManagerincludes awithdrawOrderTofunction for ORDER token withdrawals, it would make sense for the Solana proxy to also provide a mechanism to withdraw USDC.Recommendation
A
withdrawTofunction should be added toLedgerOAppto allow for the recovery of excess ETH. Additionally, awithdrawUSDCTofunction should be implemented inproxy_token_accountto ensure surplus USDC can be withdrawn when necessary.Resolution
Orderly Team: The issue was resolved in commit b670398.
No findings match.
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 -
Cross-Chain Yield Vault
64 findings7 critical · 6 high 64 findings: 7 critical, 6 high, 16 medium, 35 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.
