🤝Informational Vulnerability 14: Public to External functions

Introduction

Changing the ownership of a smart contract is a crucial operation, often necessary for the management and maintenance of decentralized applications and tokens. However, the ownership change process must be meticulously designed to avoid unexpected vulnerabilities and to ensure that the contract’s control is transferred securely and unambiguously. This tutorial aims to guide you through a best practice approach in handling ownership changes in smart contracts by implementing a two-step process.

The Risk of a Single-Step Ownership Change

In a simplistic approach, ownership is changed by merely calling a function that updates the owner's state variable. Here is how it may look:

solidityCopy codepragma solidity ^0.8.0;

contract Ownable {
    address public owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    function changeOwner(address newOwner) public {
        require(msg.sender == owner, "Not the owner");
        owner = newOwner;
    }
}

While this might seem straightforward, it's fraught with risks:

  • If the newOwner address is incorrect or compromised, the contract's ownership could be irretrievably lost or hijacked.

  • The new owner doesn’t have to confirm the reception of ownership, potentially leading to situations where the new owner is not aware of the ownership or not ready to assume responsibility.

Implementing a Two-Step Ownership Change

A safer approach involves a two-step process where the current owner nominates a new owner, and the new owner then has to accept the ownership.

1. Nomination Step:

  • The current owner nominates a new owner.

  • The contract saves this nomination without changing the actual ownership.

2. Acceptance Step:

  • The nominated new owner accepts the ownership.

  • The contract updates the actual ownership upon acceptance.

Here’s a simple implementation:

solidityCopy codepragma solidity ^0.8.0;

contract Ownable {
    address public owner;
    address public pendingOwner;
    
    constructor() {
        owner = msg.sender;
    }
    
    function nominateNewOwner(address _pendingOwner) public {
        require(msg.sender == owner, "Not the owner");
        pendingOwner = _pendingOwner;
    }
    
    function acceptOwnership() public {
        require(msg.sender == pendingOwner, "Not the nominated owner");
        owner = pendingOwner;
        pendingOwner = address(0);
    }
}

Benefits of the Two-Step Process

  • Security: It provides an additional layer of security by ensuring that the nominated address is capable of interacting with the contract.

  • Verification: The new owner has to actively accept the ownership, reducing the risks associated with accidental ownership transfers to incorrect or unprepared owners.

  • Clarity: The two-step process adds clarity regarding the ownership status, as it distinctly separates the nomination and acceptance phases.

Conclusion

Implementing a two-step process for ownership changes in smart contracts is a robust practice that enhances security and clarity. By requiring the new owner to actively accept the ownership, it mitigates risks and contributes to the safeguarding of the contract’s control and management.

Last updated