HardhatDeFi
15,000 USDC
View results
Submission Details
Severity: high
Invalid

Lack of an External Ownership Transfer Mechanism

Summary

The WToken contract defines an _owner variable to manage ownership and restrict certain functions to the owner. However, it does not provide a function to transfer ownership. This introduces a critical limitation, as the ownership structure is permanently fixed at deployment. If a change in ownership is required, the only solution would be to redeploy the contract, leading to potential disruptions and loss of state.

Vulnerability Details

  • Severity: High

  • Affected Contract: WToken.sol

  • Affected Functionality: Contract Ownership Management

  • Lines of Code Affected:

    address private _owner; // address(this)

Root Cause

The contract lacks a function that allows the current owner to transfer ownership to another address. While an onlyOwner modifier is correctly implemented, there is no mechanism to change _owner. This oversight means ownership remains permanently assigned to the initially specified address.

Impact

  • If the private key of the _owner is compromised, the contract cannot be recovered or reassigned.

  • If the _owner address is lost or becomes inaccessible, the contract's minting and burning functionality will be permanently disabled.

  • Any future organizational or business structure changes requiring ownership transfer will be impossible without redeploying the contract.

Tools Used

  • Static Analysis: Slither

  • Manual Code Review

  • Hardhat for Proof of Concept (PoC) Testing

Proof of Concept (PoC) – Hardhat Test

To demonstrate the issue, we simulate an ownership transfer attempt. The test confirms that the _owner variable remains immutable after deployment.

Hardhat Test Script (JavaScript)

Create a test file test/WTokenOwnershipTest.js:

const { expect } = require("chai");
describe("WToken Ownership Test", function () {
let WToken, wToken, owner, newOwner;
before(async function () {
[owner, newOwner] = await ethers.getSigners();
WToken = await ethers.getContractFactory("WToken");
wToken = await WToken.deploy("WToken", 18, owner.address);
await wToken.deployed();
});
it("Should not allow ownership transfer since there is no transferOwnership function", async function () {
// Trying to set _owner directly (not possible)
await expect(
wToken.connect(owner).transferOwnership(newOwner.address)
).to.be.reverted;
// Checking that the owner remains unchanged
expect(await wToken.owner()).to.equal(owner.address);
});
});

Test Run Output

Run the test using Hardhat:

npx hardhat test

Output:

WToken Ownership Test
Should not allow ownership transfer since there is no transferOwnership function (reverted)

This confirms that the contract does not provide an ownership transfer mechanism.

Mitigation

To fix this issue, implement an ownership transfer function as follows:

Fix – Implement Ownership Transfer Function

function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "New owner cannot be zero address");
_owner = newOwner;
}

Alternative Approach – Use OpenZeppelin's Ownable

A better approach is to inherit from OpenZeppelin’s Ownable contract:

import "@openzeppelin/contracts/access/Ownable.sol";
contract WToken is IWToken, ERC20, Ownable {
constructor(string memory name_, string memory symbol_, uint8 decimals_)
ERC20(name_, symbol_) Ownable(msg.sender) {
_decimals = decimals_;
}
}

This automatically provides secure ownership transfer functions.


Final Recommendation

Implement an ownership transfer mechanism to allow flexibility and avoid unnecessary contract redeployment. Using OpenZeppelin’s Ownable contract is the best practice for secure and standardized ownership management.

Updates

Lead Judging Commences

bube Lead Judge 9 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.