OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: low
Invalid

Lack of Two-Step Ownership Transfer Pattern Creates Risk of Irrevocable Loss of Administrative Control

Finding Title

Lack of Two-Step Ownership Transfer Pattern Creates Risk of Irrevocable Loss of Administrative Control

Summary

The contract inherits from OpenZeppelin's standard Ownable.sol, which facilitates ownership transfer in a single transaction (transferOwnership). This one-step process is risky because if the current owner accidentally transfers ownership to an incorrect or inaccessible address (e.g., a typo in an EOA, a smart contract with no function to accept ownership, or an address from a different network), all administrative privileges will be permanently and irrevocably lost. This poses a significant operational risk to the protocol, as critical functions like setAllowedSellToken and withdrawFees would become unusable.

Finding Description

The OrderBook contract relies on an owner to perform critical administrative functions. The ownership is managed by OpenZeppelin's Ownable.sol.

// src/OrderBook.sol:23
contract OrderBook is Ownable {
// ...
}

The standard transferOwnership(address newOwner) function in Ownable executes the transfer immediately. There is no confirmation step from the newOwner. This means a simple human error, such as a copy-paste mistake when specifying the newOwner address, can lead to a catastrophic outcome. If the ownership is transferred to an address that cannot be controlled, there is no recovery mechanism.

Scenario of Failure:

  1. The current owner intends to transfer ownership to a new multi-sig wallet at address 0xabc...def.

  2. They accidentally paste an incorrect address, 0xabc...dff, which is either a typo, an address they do not control, or a contract without ownership capabilities.

  3. They call transferOwnership("0xabc...dff").

  4. The transaction succeeds, and the ownership is instantly transferred.

  5. All administrative functions are now permanently locked, as no one can sign transactions from the incorrect newOwner address.

Impact

This vulnerability creates a single point of failure around the ownership transfer process. The impact of a mistake is irreversible and severe:

  • Permanent Loss of Control: All functions guarded by the onlyOwner modifier, including setAllowedSellToken, emergencyWithdrawERC20, and withdrawFees, become permanently inaccessible.

  • Frozen Protocol Fees: The owner will be unable to withdraw accumulated protocol fees, leading to a direct loss of revenue.

  • Inability to Manage Tokens: The owner will be unable to add support for new tokens or delist problematic ones, hindering the protocol's adaptability and security response.

Likelihood

Low. While the action of transferring ownership is infrequent, the potential for human error is always present, and the consequences are permanent.

Proof of Concept

A conceptual PoC is more appropriate here as this is a protocol design issue rather than a runtime bug. The vulnerability lies in the transferOwnership function inherited from Ownable.sol.

  1. Action: The current owner calls book.transferOwnership(incorrect_address).

  2. State Change: The _owner variable in the Ownable contract is immediately set to incorrect_address.

  3. Result: Any subsequent calls to onlyOwner functions (e.g., book.withdrawFees(owner)) will fail for the original owner. If incorrect_address is uncontrollable, these functions are lost to everyone.

Recommended Mitigation

It is strongly recommended to implement a two-step ownership transfer pattern. This ensures the recipient address is valid, accessible, and ready to accept the new role before the final transfer occurs. OpenZeppelin provides a secure, drop-in replacement with Ownable2Step.sol.

// src/OrderBook.sol
- import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+ import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
- contract OrderBook is Ownable {
+ contract OrderBook is Ownable2Step {
// Contract body remains the same
}

Migration Steps:

  1. Replace the import of Ownable.sol with Ownable2Step.sol.

  2. Change the contract inheritance from is Ownable to is Ownable2Step.

This change replaces the risky one-step transferOwnership with a safer two-step process:

  1. transferOwnership(newOwner): The current owner nominates a pendingOwner.

  2. acceptOwnership(): The nominated pendingOwner must call this function to confirm and finalize the transfer.

This prevents ownership from being lost due to simple mistakes.


Updates

Lead Judging Commences

yeahchibyke Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.