Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: medium
Likelihood: low

No Two-Step Ownership Transfer — Irrecoverable on Mistake

Author Revealed upon completion

Root + Impact

Location: src/Stratax.sol:290-293, src/StrataxOracle.sol:114-118

Description

Both Stratax and StrataxOracle implement direct single-step transferOwnership. The new owner is set immediately without requiring the recipient to confirm acceptance. A typo or misconfigured address permanently bricks all privileged functions with no recovery path.

// src/Stratax.sol:290-293
function transferOwnership(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "Invalid address");
owner = _newOwner; // @> immediate — no acceptance required from new owner
}
// src/StrataxOracle.sol:114-118
function transferOwnership(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "Invalid address");
owner = _newOwner; // @> same pattern — no two-step handover
}

Risk

Likelihood:

  • Single-step ownership transfers are a documented source of human error, particularly when copy-pasting addresses under time pressure

  • Transfer to a contract address that cannot call onlyOwner functions (multisig with wrong ABI, dead address) permanently locks the contract

Impact:

  • All onlyOwner functions become permanently inaccessible — position creation/unwinding, oracle updates, fee changes, and token recovery are all frozen

  • Positions cannot be unwound — user funds remain locked in Aave with no exit mechanism

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {Stratax} from "../../src/Stratax.sol";
contract OwnershipBrickPoCTest is Test {
Stratax stratax;
address currentOwner;
address wrongAddress = address(0xDEAD); // typo — intended 0xDEAF
function setUp() public { currentOwner = stratax.owner(); }
function test_ownershipTransferToBadAddressBricksContract() public {
vm.prank(currentOwner);
stratax.transferOwnership(wrongAddress); // @> one-step, immediate
// Contract now owned by 0xDEAD — no private key known
assertEq(stratax.owner(), wrongAddress);
// All privileged functions permanently inaccessible
vm.prank(currentOwner); // original owner can no longer call
vm.expectRevert("Not owner");
stratax.setFlashLoanFee(5);
vm.prank(currentOwner);
vm.expectRevert("Not owner");
stratax.unwindPosition(address(0), 0, address(0), 0, "", 0);
// No recovery path — contract is permanently bricked
}
}

Recommended Mitigation

Implement a two-step transfer: the current owner nominates a pendingOwner, and only after the new owner calls acceptOwnership() does the transfer complete. This ensures the recipient address is valid and can sign transactions before control is relinquished.

+ address public pendingOwner;
function transferOwnership(address _newOwner) external onlyOwner {
- require(_newOwner != address(0), "Invalid address");
- owner = _newOwner;
+ pendingOwner = _newOwner;
}
+ function acceptOwnership() external {
+ require(msg.sender == pendingOwner, "Not pending owner");
+ owner = pendingOwner;
+ pendingOwner = address(0);
+ }

Support

FAQs

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

Give us feedback!