Once upon a time, in the mythical land of Soliditium, a courageous knight named Sir Codelot embarked on a grand mission.
The Role of Access Control in Solidity Smart Contracts
His task was not to slay dragons or rescue royalty but to safeguard the kingdom's most valuable assets, their Solidity smart contracts. These were not ordinary contracts; they were a blend of magic and science, holding within them untold power and wealth.
Authorization is one of the most important applicable security layers for every project. By clearly defining rights and mapping them to the processes and resources, we achieve a clear map of how our protocol is supposed to be used.
The access control system stretches all over the whole project. From the front-end layer of a user interface to smart contracts and their functions which are the heart of every decentralized idea.
In this article, we will focus on the backend side and access control in Solidity smart contract, as this is the last line of defense which if broken might have disastrous consequences.
The main goal of this layer is to enforce expected behavior and maintain control over business flows and handled data.
Access Control Models: The Council of the Advisors
As Sir Codelot embarked on his quest, he sought counsel from the four wise advisors of Soliditium. Each advisor, seasoned in their own right, offered a different approach to safeguarding the kingdom, mirroring the different access control models at Codelot's disposal.
Discretionary Access Control (DAC)
First, the Advisor of Discretion came forth. He proposed a system where each smart contract, or kingdom resource, could be controlled by the individual or entity who owns it. The ownership allowed the power to grant access at their discretion, offering flexibility and freedom. Yet, he warned that this power came with the risk of improper access granting, leaving the kingdom vulnerable to unscrupulous elements.
This is one of the most popular models used in the world of smart contracts. The onlyOwner
modifier or a ready-made implementation with Ownable
are popular examples of it. An entity with the Owner
role has the possibility to pass its access to others through transferOwnership()
function.
Mandatory Access Control (MAC)
Next, the Advisor of Mandate offered a more stringent approach. His system would classify information and user clearances, preventing any unauthorized access. The kingdom's treasures would be under iron-clad protection. However, he cautioned that this rigid system could be cumbersome and might limit legitimate access, creating potential bottlenecks
This model is not widely spread among projects as it requires predetermining overreaching rules that could be costly to enforce. An example of such a model could be a project that gives access rights based on the reputation level of the particular user. The users would not be able to pass their clearance or revoke access to others.
Role-Based Access Control (RBAC)
The third, the Advisor of Roles, suggested a system where roles were assigned to each citizen of Soliditium. These roles would dictate their access level, creating a clear, manageable hierarchy. However, he noted that this model might oversimplify access control, neglecting the nuances of individual access needs.
The model gained popularity soon after the DAC by introducing a more flexible and granular way to assign permissions through the AccessControl
contract.
Attribute-Based Access Control (ABAC)
Finally, the Advisor of Attributes, presented his solution. His model would grant access based on a combination of attributes, such as role, department, and time of the request, offering a highly granular and customizable system. Yet, he acknowledged that this high level of customization could complicate the system, making it harder to manage.
Consider this model as an extension of RBAC. An example of this may be the combined use of AccessControl
with a Pausable
, which in addition to checking the role, verifies additional requirements such as whenNotPaused
and whenPaused
before performing the operation.
With these valuable insights, Sir Codelot was better equipped to choose the most suitable access control model for his mission, understanding that each offered unique strengths and challenges. The choice would indeed be a strategic one, defining the kingdom's security and the future of Soliditium.
The Principle of Least Privilege: The Humble Squire's Lesson
While Sir Codelot was in deep discussion with the Council of Advisors, a humble squire entered the grand meeting room. The young lad, though unassuming, was known for his quick wit and sharp intellect. Bowing before the council, he respectfully proposed his thoughts on safeguarding the kingdom.
The squire spoke of the Principle of Least Privilege, a simple yet effective strategy that he had often observed during his duties. He suggested that each knight, scribe, and servant of Soliditium should only be granted the minimum privileges necessary to fulfill their roles.
In doing so, the chances of treachery or mishap would be greatly reduced, as each person could affect only their sphere of influence and nothing more.
To explain further, he told a tale of a former head cook who was given the keys to the entire castle in order to access the kitchens. Unfortunately, a rogue stole the keys and wreaked havoc in the kingdom before being captured. Had the cook been given only the kitchen key, the damage could have been drastically reduced.
The approach we suggest when designing any system is closely aligned with the principle of minimum privileges.
After defining the roles that occur in your project, go function by function and fill in temporary comments to clearly define the matrix of permissions.
It might look like this:
Then, implement rights according to the created permission matrix and make sure that you verify them within unit tests. This will not only help with implementation but also save a lot of time dedicated to audit.
You can remove these comments later, before publishing the code, but leave them at least until the audit is finished.
Listening intently, Sir Codelot and the advisors found wisdom in the squire's words. By implementing the Principle of Least Privilege in their access control models, they could minimize the potential damage from a breach, thereby adding an extra layer of security to protect the kingdom's precious resources.
Navigating Decentralized Access Control in Smart Contracts: The Maze of Freedom
Sir Codelot was pleased with what he had learned so far. However, there was still something missing in this approach. He thought of the king's health and age and realized that he didn't have much time left. After his death, the power may fall into the wrong hands.
He wanted to prepare the precious Smart Contracts for that and create a more open, democratic model where roles could be dynamically assigned or revoked, not necessarily by a centralized government.
The maze of decentralized governance, however, was a different beast altogether. To navigate this dynamic labyrinth, a new kind of map was required.
The method of managing the system is an inseparable element of access control, which also needs to be considered when designing.
A typical model for achieving gradual decentralization is a two-step process. In the beginning, teams hand over more privileged roles to multi-sig (at least 2 out of 3) smart contracts. The reason behind this is to maintain speed and control at early stages, but when projects mature and gain the right traction, they ultimately transfer it to the DAO, which should be managed by the community involved in the project.
Of course, there are also other models, e.g. gradual decentralization through vesting and carefully planned distribution of tokens, but let's leave that for another time.
Secure Transitions: The King's Succession
The wise old Advisor 'Scure' proposed a solution. Instead of directly passing the throne to the successor, a two-step transition process should be put in place. The successor would first be proposed and thoroughly scrutinized by the kingdom's council, a period akin to a 'pendingOwnershipTransfer'. Only when all parties were satisfied would the actual transfer of power take place.
Sir Codelot likened this to the idea of two-step ownership transfers in smart contracts. Rather than immediately granting full control of a contract to a new owner, the process was broken down into 'propose new owner' and 'accept ownership' stages. This additional layer of security would safeguard the contract from sudden unauthorized changes, giving the original owner and relevant parties time to validate the proposed changes or intervene if necessary.
While management is being transferred to other hands, it is important that this process ensures secure succession. Merely changing the owner's address is not enough.
Worst of all preventable scenarios in such cases are:
- Transfer of power to the wrong address, without the possibility of correction.
- Loss of management capabilities by e.g. transferring power to an inactive address.
That's why it's worth it to be a 2-step process. Which, after designating the address of the new owner, enforces accepting this role and the responsibility it carries.
Thanks to this, we will at least partially avoid the indicated threats.
As the rain pattered gently against the castle windows, the council members found wisdom in Advisor 'Scure's words, nodding their agreement. Just as the kingdom required a secure process for a smooth transition of power, so did their smart contracts required additional functions to bolster their security in the realm of access control. And so, another layer of protection was added to Soliditium's mighty shield.
Wisdom from the Wizard: The OpenZeppelin's v4.9 smart contracts
A heaviness loomed in the grand meeting room, an echo of Sir Codelot's troubled thoughts. His mind raced with all the intricacies of access control, the countless checks and balances, each a safeguard against treachery. As he pondered the enormity of the task before him, a thought crept into his mind, casting a shadow of doubt. Would it not take years to build and verify the security of their smart contracts?
Just as a glimmer of despondency began to creep into Sir Codelot's spirit, there came a low, soothing voice from the corner of the room. Emerging from the shadows, the King's old and trusted wizard stepped into the flickering candlelight, his eyes twinkling with knowledge and a hint of a mysterious secret.
"Fear not, brave Sir Codelot," the wizard began, his voice gentle yet firm. "For you need not tread this path alone. The journey to secure our kingdom's smart contracts need not span years."
The wizard raised his staff and with a swift gesture, ethereal glyphs of an ancient language illuminated the room, swirling around to form the words 'OpenZeppelin v4.9'. The wizard explained that these were pre-existing, thoroughly vetted smart contracts that would save precious time and offer robust security.
The most trusted and widely used OpenZeppelin contracts contain an entire section dedicated to access control. You can find all the contracts here, and if you want to look at the documentation, it's here.
What's more, in the recent v4.9 update they even improved the default AccessControl
to better handle the default admin role.
Let's go through the individual contracts and observe how they implement the access control security principles discussed in this article.
Ownable.sol
An extension that introduces Discretionary Access Control. The simplest of all contracts. It allows you to extend the operation of the contract with the possibility of indicating its owner.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
Advantages:
- Very simple.
- 1 transaction is enough to change the owner.
- extracts the possibility of leaving the contract without an owner by a separate function
renounceOwnership()
. This is a very good practice to eliminate the erroneous passing of theaddress(0)
as a parameter.
Disadvantages:
- the possibility of a loss of management when transferring the ownership to the wrong address (lack of a 2-step process).
- small granularity, introduces only the owner role.
To use it, simply import it:
import "@openzeppelin/contracts/access/Ownable.sol";
and add modifier onlyOwner
to functions to which you want to restrict access. The default owner will be set to the deployer’s address.
Ownable2Step.sol
Also, an extension that introduces Discretionary Access Control. The extension of Ownable
with a 2-step transfer ownership process. It allows you to extend the operation of the contract with the possibility of indicating its owner.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol
Advantages:
- Very simple.
- 2-step process allows mitigating risk related to inactive and invalid addresses.
- extracts the possibility of leaving the contract without an owner by a separate function
renounceOwnership()
. This is a very good practice to eliminate the erroneous passing of theaddress(0)
as a parameter.
Disadvantages:
- 2 transactions are required to change the owner.
- small granularity, introduces only the owner role.
To use it, simply import it:
import "@openzeppelin/contracts/access/Ownable2Step.sol";
and add modifier onlyOwner
to functions to which you want to restrict access. The default owner will be set to the deployer’s address. The pendingOwner
will not have power until they accept the ownership through acceptOwnership()
function.
AccessControl.sol
An extension that introduces Role-Based Access Control. It gives more flexibility and possibilities to manage the system through various defined roles. Besides setting the adminRole it also allows for granting and revoking permissions.
Advantages:
- good granularity, enables multiple roles.
- flexible permission management through granting and revoking.
Disadvantages:
- the possibility of a loss of management when transferring the
adminRole
to the wrong address (lack of a 2-step process).
To use it, simply import it:
import "@openzeppelin/contracts/access/AccessControl.sol";
Define roles as bytes32
identifiers:
bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
and use add custom errors in the selected functions:
function foo() public {
if(!hasRole(MY_ROLE, msg.sender));
revert AccessDeniedMustHaveMyRole();
…
}
AccessControlEnumerable.sol
An extension that besides AccessControl
functionality also allows enumerating all privileged accounts through additional getRoleMember()
and getRoleMemberCount()
functions. It is especially useful when you need to have simple access to all members with a particular role, e.g. to present it on the manager’s dashboard.
Advantages:
- enables easy access to data such as addresses of particular role members
- good granularity, enables multiple roles.
- flexible permission management through granting and revoking.
Disadvantages:
- the possibility of a loss of management when transferring the adminRole to the wrong address (lack of a 2-step process).
To use it, simply import it:
import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
As in AccessControl
define roles as bytes32
identifiers:
bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
and use add custom errors in the selected functions:
function foo() public {
if(!hasRole(MY_ROLE, msg.sender));
revert AccessDeniedMustHaveMyRole();
…
}
AccessControlDefaultAdminRules.sol
An extension that introduces Attribute-Based Access Control (ABAC). Novelty, which appeared in version v4.9. A security extension to AccessControl
that mitigates various risks through additional functionality.
A few examples are threats are minimized in the following way:
-
The
DEFAULT_ADMIN_ROLE
set in the constructor is the only address with this role and cannot assign it to anyone else. It can only renounce it and leave the protocol in the hands of the other roles.This sets the protocol up so that it does not need centralized management in its final version.
-
Role management has been divided into two separate flows. One strictly supports administrative functions through the
beginDefaultAdminTransfer()
function, and the othergrantRole()
, which allows managing all other roles.Thanks to this, only one address can have such high privileges and such changes can be handled slower and more accurately with designed delays.
-
The
cancelDefaultAdminTransfer()
introduces the possibility of canceling an unsuccessful selection of a successor.
Advantages:
- security focused
- good granularity, enables multiple roles.
- flexible permission management through granting and revoking.
Disadvantages:
- the most complicated
- requires thought and adjustments ie. defaultAdminDelay
To use it, simply import it:
import "@openzeppelin/contracts/access/AccessControlDefaultAdminRules.sol";
As in AccessControl
define roles as bytes32
identifiers:
bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
and use add custom errors in the selected functions:
function foo() public {
if(!hasRole(MY_ROLE, msg.sender));
revert AccessDeniedMustHaveMyRole();
…
}
This is one of the best options for managing permissions available and our recommendation for the most extensive protocols.
Big kudos to the OpenZeppelin team that made such an extension.
Sir Codelot felt a surge of hope as the words sank in. "So, we don't have to start from scratch?" he asked, to which the wizard nodded, a knowing smile on his face. With the wisdom of the OpenZeppelin at their disposal, they could secure their kingdom faster and more efficiently than they had ever thought possible. And at that moment, Sir Codelot knew that their smart contracts would be as mighty and impenetrable as the walls of Soliditium itself.
- Did you like the article? Share it on social media!
Composable Security 🇵🇱⛓️ is a Polish company specializing in increasing the security of projects based on smart contracts written in Solidity. Examples of projects that have trusted us are market leaders such as FujiDAO, Enjin, or Tellor. We are creators of the Smart Contract Security Verification Standard. Speakers at various conferences such as EthCC, ETHWarsaw, or OWASP AppSec EU. Authors of numerous publications on DeFi security. Experienced auditors operating in the IT Security space since 2016.
If you need support in the field of security or auditing smart contracts do not hesitate to contact us.
About the author
Co-author of SCSVS and White Hat. Professionally dealing with security since 2017 and since 2019 contributing to the crypto space. Big DeFi fan and smart contract security researcher.