Points to note in Openzeppelin Contracts 5.0
Openzeppelin recently released version 5 of the solidity libraries. In this article, you, as a developer will get to know the imperative changes and updates.
The minimum solidity version for using OZ libraries is also now bumped up to 0.8.20. You might face problems working with this version on different chains, which is a temporary issue, that will vanish in some time. More details here.
To start with, we’ll look into the most common contracts, i.e. token contracts.
No more Token Hooks
A significant change can be seen in the way hooks work.
The hooks have been removed from all three token contracts, and a new single function _update
will now be used.
The main purpose of hooks was to introduce additional logic while transferring, minting, or burning the tokens.
However, the approach with one single _update
function will now require us to override a single function instead of overriding transfer, mint, or burn separately. The _update
function looks like this:
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
_totalSupply -= value;
}
} else {
unchecked {
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
Now this function can be used as a single entry point for any kind of transfer taking place, and we can override this, adding any custom logic, instead of overriding multiple functions.
Note that, this change is being done for all three token standards i.e. ERC20, ERC721, and ERC1155.
This will improve the gas optimization, security, and simplicity of the contracts.
Apart from this, the increaseAllowance
and decreasAllowance
functions are now deprecated from the ERC20 contract. Reason being, they are not a part of the ERC20 standard and opens up some potential vulnerabilities. Discussion.
Changes in Ownable
As we know, while using the ownable
contract, the constructor, by default sets the deployer
as the owner, this is not a good practice as the deployer might not be supposed to be the owner in some cases like deployin via factory contract or we want a multi-sig to be the owner, so we need to set the owner address explicitly after deployment. Which is a redundant step.
However, in V5 the constructor now accepts an address as an argument allowing the deployer
to explicitly set the owner.
//Old ownable Constructor
constructor() {
_transferOwnership(_msgSender());
}
//New ownable constructor
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
Removed functions and contracts
There are a couple of contracts that are removed in this version for different reasons. Some of them, that were popular and mostly used by us are:
- Address.isContractThis was removed because of the fact that a contract can be interpreted as an EOA by this method, which can lead to misunderstanding, which can later lead to exploits.
- CountersCounters were mainly used for having a variable for incrementing and decrementing purposes. But its usage is not significant in the development space, as it doesn’t add much value to any product, needlessly increasing the complexity and gas usage. UDVTs are a good alternative for this library
- SafeMath and SignedSafeMathThis is the most popular library. And we all know that solidity 0.8.0 has introduced these checks by default.
- ERC165StorageThis was mainly used for registering an interface similar to what ERC165 actually offers. But it introduced more gas usage, and it was not widely accepted. To know more about ERC165 you can check this article.
- ERC777ERC777 is known to be “over-engineered”, and vulnerable to attacks. This standard was not widely accepted and adapted by the community. Know more about ERC777.
You can find the other removed contracts here.
Custom errors and explicit imports.
All the revert strings and require statements are now replaced with custom errors, as custom are more cheaper and more flexible by supporting dynamic output.
Explicit imports are something that is suggested by everyone in recent days, as it prevents from populating the global scope. It is also considered a better, clean syntax.
Namespaced Storage for better upgrades security
Previously the upgradeable contract used to have gaps in between storage variables, to add more variables in later versions.
Now these gaps are replaced by namespaced storage structure which is inspired from ERC2535 diamond storage.
We call it namespaced storage to avoid confusion with the diamond proxy pattern, because they can be used independently, despite of having the same name.
Namespaced storage pattern uses an struct to store the variables, and that struct is stored in a storage slot that is calculated like this:
keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
So that the storage allocation doesn’t start with slot 0.
This namespaced pattern is proposed as an ERC by the OZ team as ERC7201.
New Access Manager to control everything from one contract.
Access control contracts got a major update.
We now have two sub-directories inside the Access directory, one of them is extensions
that contain the old extensions, for a better structure, while the other one is manager
that contains the newly released AccessManager
contract and its dependencies.
AccessManager
now makes it possible to manage multiple contracts from one single contract.
It defines two major contracts AccessManager
for the manager contract and AccessManaged
for the contracts are managed, also called target contracts.
AccessManaged
attaches a modifier named restricted
, to the target contract, which can then be attached to the external
(or public
function which is not called internally) function. The mechanism has similar working like the old accessControl
contract but the feature of having multiple target contracts makes it a bit complicated.
Alright decipherers, hope that was helpful. Thanks 😊