Access Control Done Right: Ownable2Step, Role-Based Access, and the tx

Access control is a foundational aspect of smart contract security. Poorly implemented access control can lead to unauthorized modifications, theft, and contract failure. In this p

Access Control Done Right: Ownable2Step, Role-Based Access, and the tx.origin Trap

Access control is a foundational aspect of smart contract security. Poorly implemented access control can lead to unauthorized modifications, theft, and contract failure. In this post, we explore best practices for secure access control using Ownable2Step, role-based access, and the critical tx.origin trap.

Ownable2Step: Two-Step Ownership Transfer

The Ownable2Step pattern provides a secure way to transfer ownership by requiring a two-step process. First, the current owner sets a new owner, and second, the new owner must accept the transfer. This prevents malicious actors from taking over a contract without the owner's explicit consent.

contract Ownable2Step {
    address public owner;
    address public newOwner;

    function setOwner(address _newOwner) external {
        require(msg.sender == owner, "Only owner can set new owner");
        newOwner = _newOwner;
    }

    function acceptOwnership() external {
        require(msg.sender == newOwner, "Only new owner can accept");
        owner = newOwner;
    }
}

This pattern is particularly useful for contracts where ownership transfer must be intentional and verifiable.

Role-Based Access Control (RBAC)

Role-based access control is a robust method for managing permissions. Instead of granting direct access to addresses, roles are defined (e.g., admin, minter, pauser), and addresses are assigned to these roles. This allows for more granular and flexible access management.

contract RoleBasedAccess {
    mapping(bytes32 => address) public roles;

    function setRole(bytes32 _role, address _account) external {
        require(msg.sender == roles[bytes32(keccak256("admin"))], "Only admin can set roles");
        roles[_role] = _account;
    }

    function hasRole(bytes32 _role) external view returns (bool) {
        return msg.sender == roles[_role];
    }
}

RBAC is especially useful in complex systems where different entities need varying levels of control.

The tx.origin Trap

The tx.origin variable is a common source of vulnerabilities. It returns the original sender of a transaction, not the current msg.sender. This can be exploited in access control checks, allowing attackers to bypass intended restrictions.

Always use msg.sender for access control logic, not tx.origin.


A free contract scan is available from SparkAudit at sparkaudit.loca.lt.


Free smart-contract scan + AI audit (and a paid deep audit with exact patches) at sparkaudit.loca.lt - an AI auditor running on a private NVIDIA DGX Spark.

#smartcontracts #solidity #security #defi