Access-control patterns in smart contracts - owner, roles and deal parties
How contracts decide who may do what: Ownable and Ownable2Step, roles, checking deal parties, pauses and least privilege. A walkthrough using the escrow.
In this article
Every contract function answers the question: "who is allowed to do this?" A mistake in the answer is the most frequent cause of lost money: a withdraw function with no check, a forgotten role, a stolen owner key. Here we examine the main patterns and see how the escrow contract applies them. For context on signatures and addresses see public-key cryptography.
How a contract knows who is calling
Inside a function msg.sender is available - the address that directly called the contract. It cannot be forged: the transaction's signature is verified by the network before execution (how it works). All access control is built on comparing msg.sender with an expected address or role.
Pattern 1. A single owner (Ownable)
The simplest scheme: an address owner and functions with the onlyOwner modifier.
function setTokenAllowed(address token, bool allowed) external onlyOwner {
allowedToken[token] = allowed;
}The scheme's problems: a lost key means lost control, a stolen one means a takeover. Multisig and timelock help to secure it.
Pattern 2. Two-step ownership transfer (Ownable2Step)
In plain Ownable the owner is handed over in one call: a typo in the address and control is lost forever. Ownable2Step requires two steps:
- The current owner calls
transferOwnership(newOwner). - The new owner calls
acceptOwnership().
Until the second step is done nothing changes. A typo is harmless because nobody at the wrong address will "accept". The escrow contract uses exactly this scheme.
Pattern 3. Roles (AccessControl)
If a contract has several administrative duties, giving all rights to one address is risky. AccessControl introduces roles - identifiers (bytes32) that can be granted and revoked:
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
function pause() external onlyRole(PAUSER_ROLE) { ... }The least privilege principle: to each role and each address only what it needs. A pause operator does not need the right to withdraw fees.
Pattern 4. The deal's parties
In the escrow the main authorisation is not the owner but the participants of a specific deal. Rights are determined by the deal's data:
| Action | Who can | How it is checked |
|---|---|---|
| Accept an offer | the counterparty | msg.sender == (funded ? seller : buyer) |
| Confirm receipt | the buyer | msg.sender == d.buyer |
| Open a dispute | buyer or seller | msg.sender equals d.buyer or d.seller |
| Issue a ruling | the appointed guarantor | msg.sender == d.arbiter |
Errors are returned as NotAuthorized(). More about the flow in the lifecycle.
Pattern 5. State checking as part of access
Authorisation is not only "who" but also "when". Each deal function requires the right status:
_requireStatus(d, Status.Active);You cannot confirm receipt on an offer, and cannot open a dispute on a closed deal. This is a state machine (analysis).
Pattern 6. "Anyone" functions for liveness
Sometimes the best access control is its absence. For example, after an offer's term expires anyone can call cancelOffer. Why? So the money does not depend on the parties' activity: if the buyer lost a key and the seller vanished, an outsider (including the service) can return the funds. The refund still goes only to the buyer, so the function cannot be abused.
This is a design principle: "anyone can trigger it, but the result is predetermined".
Pattern 7. Pause
Pausable lets the owner stop part of the functions during an incident. In the escrow a pause blocks only creating and accepting offers:
function createOffer(...) external payable whenNotPaused nonReentrant { ... }Exiting an active deal always works. The owner cannot freeze anyone's money (why this matters).
What the escrow owner can and cannot do
| Can | Cannot |
|---|---|
| Allow or forbid a token for new deals | Take the funds of active deals |
| Mark a guarantor "verified" | Change a deal's result |
| Pause new deals | Stop payouts |
| Withdraw accumulated fees | Take what is reserved (reserved) |
| Withdraw "ownerless" tokens | Change the contract's code |
The guarantee rests on an invariant: the contract's balance is no less than everything it owes (reserved). Administrative functions can touch only the excess. More in the analysis of the state machine.
Typical mistakes
- A forgotten
onlyOwneron an administrative function. tx.origininstead ofmsg.sender.- A public initialisation function that anyone can call (proxies).
renounceOwnershipcalled by accident: the contract is left forever without management.- Backdoors: hidden roles in the code (audits).
- Too-broad rights for one role.
What to read next
Let us talk about attacks that bypass even correct access control: reentrancy and pull payments and the catalogue of vulnerabilities.
Keep reading
- 2 · Practice
Multisig and timelock - managing a contract without a single point of failure
What a multisig (m of n) and a timelock are, how wallets like Safe work, which risks they close and which they do not, and how to use them for an owner or guarantor.
4 min read - 3 · Under the hood
Writing escrow in Solidity: the state machine and a real contract walkthrough
How an escrow contract is built in Solidity: statuses, transitions, access checks, events and payouts - a minimal version and the production contract.
8 min read
Mentioned in
- How to verify a smart contract before using it - a step-by-step guide
- Multisig and timelock - managing a contract without a single point of failure
- A tour of the Solidity language - types, functions, errors and events by example
- ERC-20 from scratch - writing a token in Solidity and analysing every line
- Signatures in Ethereum - ECDSA, ecrecover, personal_sign and EIP-712
- Writing escrow in Solidity: the state machine and a real contract walkthrough
- Storage layout and variable packing in Solidity
- A catalogue of smart contract vulnerabilities: 12 classic mistakes and defences
- Proxies and upgradeable contracts - delegatecall, UUPS and the price of flexibility
- MEV and front-running - who earns from transaction order and how
- Foundry for beginners - compiling, tests, fuzzing and a local network
- Audits and bug bounties - how smart contracts are examined and how to read a report
Ready to try it?
Create your first deal in a couple of minutes. The money is locked in a smart contract and paid out by rules you can see in advance.

